Simulating Exploding Dice

31

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.

Rɪᴋᴇʀ

Posted 2019-04-12T14:09:05.373

Reputation: 7 410

1does the program have to be perfect? Like can its distribution be off by some extremely low amount? – Maltysen – 2019-04-12T14:38:57.233

To: Riker; RE: @Maltysen's comment above; or extremely high amount? – Artemis still doesn't trust SE – 2019-04-12T15:09:12.753

2

@ArtemisFowl See our standards for randomness. Also, here.

– Rɪᴋᴇʀ – 2019-04-12T22:46:03.607

Answers

36

x86 Machine Code (for Intel Ivy Bridge and later), 17 bytes

31 C9 0F C7 F0 31 D2 F7 F6 42 01 D1 39 F2 74 F2 C3

The above bytes of code define a function that simulates an exploding die. It takes a single input, passed in the ESI register, indicating the maximum number of the die. It returns a single value in the ECX register, which is the result of the rolls.

Internally, it uses the RDRAND instruction to generate a random number. This uses a random number generator (RNG) that is built into the hardware on Intel Ivy Bridge processors and later (some AMD CPUs also support this instruction).

The logic of the function is otherwise quite straightforward. The generated random number is scaled to lie within the desired range using the standard technique ((rand % dieSize) + 1), and then it is checked to see if it should cause an explosion. The final result is kept in an accumulator register.

Here is an annotated version showing the assembly language mnemonics:

           unsigned int RollExplodingDie(unsigned int dieSize)
31 C9        xor     ecx, ecx    ; zero-out ECX, which accumulates the final result
           Roll:
0F C7 F0     rdrand  eax         ; generate a random number in EAX
31 D2        xor     edx, edx    ; zero-out EDX (in preparation for unsigned division)
F7 F6        div     esi         ; divide EDX:EAX by ESI (the die size)
                                 ;   EAX receives the quotient; EDX receives the remainder
42           inc     edx         ; increment the remainder
01 D1        add     ecx, edx    ; add this roll result to the accumulator
39 F2        cmp     edx, esi    ; see if this roll result should cause an explosion
74 F2        jz      Roll        ; if so, re-roll; otherwise, fall through
C3           ret                 ; return, with result in ECX register

I am cheating a bit. All standard x86 calling conventions return a function's result in the EAX register. But, in true machine code, there are no calling conventions. You can use any registers you want for input/output. Using ECX for the output register saved me 1 byte. If you want to use EAX, insert a 1-byte XCHG eax, ecx instruction immediately before the ret instruction. This swaps the values of the EAX and ECX registers, effectively copying the result from ECX into EAX, and trashing ECX with the old value of EAX.

Try it online!

Here's the equivalent function transcribed in C, using the __builtin_ia32_rdrand32_step intrinsic supported by GCC, Clang, and ICC to generate the RDRAND instruction:

#include <immintrin.h>

unsigned int RollExplodingDie(unsigned int dieSize)
{
    unsigned int result = 0;
Roll:
    unsigned int roll;
    __builtin_ia32_rdrand32_step(&roll);
    roll    = ((roll % dieSize) + 1);
    result += roll;
    if (roll == dieSize)   goto Roll;
    return result;
}

Interestingly, GCC with the -Os flag transforms this into almost exactly the same machine code. It takes the input in EDI instead of ESI, which is completely arbitrary and changes nothing of substance about the code. It must return the result in EAX, as I mentioned earlier, and it uses the more efficient (but larger) MOV instruction to do this immediately before the RET. Otherwise, samezies. It's always fun when the process is fully reversible: write the code in assembly, transcribe it into C, run it through a C compiler, and get your original assembly back out!

Cody Gray

Posted 2019-04-12T14:09:05.373

Reputation: 2 639

12

R, 39 bytes

n=scan();n*rgeom(1,1-1/n)+sample(n-1,1)

Try it online!

Explanation: this solution avoids recursion/while loops by directly calculating the distribution of the number of explosions that will occur. Let \$n\$ be the number of sides on the die. If you denote success as rolling an \$n\$ and failure as rolling anything else, then you have probability \$\frac1n\$ of success. The total number of explosions is the number of successes before the first failure. This corresponds to a \$\mathrm{Geometric}\left(1-\frac{1}{n}\right)\$ distribution (see the wikipedia page, which defines success and failure the other way round). Each explosion brings \$n\$ to the total. The final roll follows a \$\mathrm{Uniform}(1,2,\ldots,n-1)\$ distribution which we add to the total.

Robin Ryder

Posted 2019-04-12T14:09:05.373

Reputation: 6 625

very nice! Gotta love the builtin distributions for [tag:random] challenges! – Giuseppe – 2019-04-12T15:35:02.737

Does sample meet the standards for randomness, given its bias?

– Xi'an – 2019-04-25T08:37:20.577

@Xi'an Pretty sure it does: it is the built-in random generator for discrete random variables.

– Robin Ryder – 2019-04-25T22:37:06.317

I know, I know, but check the link I put: the discretisation inherent to sample leads to a lack of uniformity that gives a ratio of max to min probability as high as 1.03... Shocking, isn't it?! – Xi'an – 2019-04-26T07:28:43.770

Yes, it is shocking. But then again, how often do you use sample with $m\approx 2^{31}$? ;-) – Robin Ryder – 2019-04-26T22:28:30.240

12

Python 2, 66 64 61 bytes

-3 bytes thanks to xnor

f=lambda n,c=0:c%n or c+f(n,randint(1,n))
from random import*

Try it online!

The previous roll is stored in c, allowing us to access it multiple times without having to store it to a variable, which can't be done in a Python lambda. Each recursion, we check if we rolled exploding dice.

c is initialised to zero, so c%n is falsey there. In the next iterations, it will only be falsey if exploding dice were rolled.

Python 2, 55 bytes

f=lambda n:randint(1,n)%n or n+f(n)
from random import*

Try it online!

My other answer seems to be a bit overengineered, since this appears to work as well... I'll leave it anyway.

ArBo

Posted 2019-04-12T14:09:05.373

Reputation: 1 416

2Recursive functions where the breaking condition is based on randomness will always have a non-zero chance of stack overflow. Statistically insignificant chance, but still... – mypetlion – 2019-04-12T19:23:49.500

3Typically, stack size is assumed to be infinite in code golfing challenges in my experience. As the stack size increases to infinity, the likelihood of stack overflow quickly converges to zero. – ArBo – 2019-04-12T20:07:26.950

ArBo do @mypetlion before you type in your comment so you can ping the user – MilkyWay90 – 2019-04-13T02:04:25.540

1I think c*(c<n) can be c%n. – xnor – 2019-04-13T02:28:25.170

@xnor Of course, I'm an idiot... – ArBo – 2019-04-13T03:40:46.067

9

Perl 6, 26 bytes

{sum {roll 1..$_:}...*-$_}

Try it online!

Explanation

{                        } # Anonymous block
                  ...      # Sequence constructor
     {roll 1..$_:}         #   Next elem is random int between 1 and n
                           #   (Called as 0-ary function with the original
                           #   $_ for the 1st elem, then as 1-ary function
                           #   with $_ set to the previous elem which
                           #   equals n.)
                     *-$_  #   Until elem not equal to n (non-zero difference)
 sum                       # Sum all elements

nwellnhof

Posted 2019-04-12T14:09:05.373

Reputation: 10 037

2

Nice, my own solution was {sum roll(*,1..$_)...$_>*}

– Jo King – 2019-04-12T15:08:40.193

9

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 toyand addx` 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.

Conor O'Brien

Posted 2019-04-12T14:09:05.373

Reputation: 36 228

1(+$:)^:=1+?­­ – ngn – 2019-04-13T01:26:50.587

@ngn Thanks! Incorporated. – Conor O'Brien – 2019-04-14T18:00:40.100

7

Jelly, 7 bytes

X+ß}¥=¡

Try it online!

Uses recursion. Runs the program again (ß) and adds (+) if (¡) the random number (X) is equal (=) to the program input. } makes ß act on the program input and ¥ combines +ß} into a single link for ¡ to consume.

Here a distribution of 1000 outputs for n=6 which I collected using this program. Plotted with python/matplotlib. histogram

Here is a 5000 data points from n=3 on a semilog plot which shows the (approximately?) exponential distribution. enter image description here

dylnan

Posted 2019-04-12T14:09:05.373

Reputation: 4 993

Nice plots! The distribution you get is a geometric distribution (see my R answer), which is closely related to the exponential distribution.

– Robin Ryder – 2019-04-14T21:42:15.967

6

Pyth - 12 11 bytes

Uses functional while. I feel like there should be a smarter answer that just simulates the distribution.

-.W!%HQ+hOQ

-         (Q)         Subtract Q. This is because we start Z at Q to save a char
 .W                   While, functionally
  !                   Logical not. In this case, it checks for 0
   %HQ                Current val mod input
  +     (Z)           Add to current val
   h                  Plus 1
    OQ                Random val in [0, input)

Try it online.

Maltysen

Posted 2019-04-12T14:09:05.373

Reputation: 25 023

4

Python 3, 80 bytes

import random as r,math
lambda n:int(-math.log(r.random(),n))*n+r.randint(1,n-1)

Try it online!

Lynn

Posted 2019-04-12T14:09:05.373

Reputation: 55 648

1There's a slight chance for failure if r.random() happens to return 0. 1-r.random() should work, though. – nwellnhof – 2019-04-12T16:38:59.227

Though technically that chance is 0 – Quintec – 2019-04-12T20:45:53.727

1A rare case where import ... as _ is shortest! – xnor – 2019-04-13T02:24:17.017

@xnor indeed! The only other time I remember that winning out in an answer of mine is here

– Lynn – 2019-04-13T15:48:39.970

4

05AB1E, 10 bytes

[ILΩDIÊ#}O

Try it online or verify the lists.

10 bytes alternative:

[LΩDˆÊ#}¯O

Try it online or verify the lists.

Although I like the top one more because it got the 'word' DIÊ in it, which suits the challenge.

Explanation:

[         # Start an infinite loop:
 IL       #  Create a list in the range [1, input]
   Ω      #  Pop and push a random value from this list
    D     #  Duplicate it
     IÊ   #  If it's NOT equal to the input:
       #  #   Stop the infinite loop
}O        # After the loop: sum all values on the stack
          # (which is output implicitly as result)

[         # Start an infinite loop
 L        #  Create a list in the range [1, (implicit) input]
  Ω       #  Pop and push a random value from this list
   Dˆ     #  Add a copy to the global_array
     Ê    #  If it's NOT equal to the (implicit) input:
      #   #   Stop the infinite loop
}¯        # After the loop: push the global_array
  O       # Pop and push its sum
          # (which is output implicitly as result)  

Kevin Cruijssen

Posted 2019-04-12T14:09:05.373

Reputation: 67 575

Was trying to think of a way to use or something. – Magic Octopus Urn – 2019-04-22T17:24:43.567

3

R, 47 42 bytes

function(n){while(!F%%n)F=F+sample(n,1)
F}

Try it online!

Credit to ArBo's approach.

Still a byte longer than Robin Ryder's, go upvote his!

Giuseppe

Posted 2019-04-12T14:09:05.373

Reputation: 21 077

Interesting, I reworked this to a recursive if for 46 bytes, but ended up getting a 52 on one roll which shouldn't be possible with n=4, so I don't know if there's a weird low recursion limit thing happening, but I think it may be buggy. Try it online!

– CriminallyVulgar – 2019-04-12T14:45:39.100

I tried a recursive and got a 54 byte solution. Then tried something similar to yours for 44 Try it online!

– Aaron Hayman – 2019-04-12T16:48:19.133

3

Wolfram Language (Mathematica), 50 bytes

R@#//.x_/;x~Mod~#==0:>x+R@#&
R=RandomChoice@*Range

Try it online!

J42161217

Posted 2019-04-12T14:09:05.373

Reputation: 15 931

3

Ruby, 35 bytes

->n,s=0{s+=x=1+rand(n);x<n||redo;s}

Try it online!

Reinstate Monica -- notmaynard

Posted 2019-04-12T14:09:05.373

Reputation: 1 053

Save a byte by dropping the x variable: Try it online!

– benj2240 – 2019-04-12T22:55:03.853

3

Haskell, 77 76 bytes

import System.Random
f x=randomRIO(1,x)>>=(x!)
x!y|y<x=pure y|0<1=(y+)<$>f x

Try it online!

Thanks to killmous for one byte.

If <|> were in the prelude, we could do better with MonadComprehensions:

Haskell, non-competing, 66 bytes

import System.Random
f x=do y<-randomRIO(1,x);[y|y<x]<|>(y+)<$>f x

Try it online!

dfeuer

Posted 2019-04-12T14:09:05.373

Reputation: 1 016

1You can save a byte if you define g as an infix function. – killmous – 2019-04-12T19:38:51.803

1@killmous, thanks. At first glance I figured that would be the same or worse, but it's better. – dfeuer – 2019-04-12T21:02:55.473

3

APL (Dyalog Unicode), 15 14 bytes

{×r←?⍵:r⋄⍵+∇⍵}

Try it online!

ngn

Posted 2019-04-12T14:09:05.373

Reputation: 11 449

3

Python 2, 53 bytes

f=lambda n:random()*n//1or n+f(n)
from random import*

Try it online!

Uses the or short-circuiting idea from ArBo's answer. The expression random()*n//1 generates a number from 0 to n-1, with 0 taking the place of a roll of n. The or takes the that number, except if it's zero (Falsey) it continues on to n+f(n).

xnor

Posted 2019-04-12T14:09:05.373

Reputation: 115 687

It seems your answer was already up when I edited in my shorter one... I didn't see this, but if you want me to delete it because it's quite alike I will. – ArBo – 2019-04-13T18:05:27.093

@ArBo Not at all, independently duplicated answers are fine.

– xnor – 2019-04-13T18:07:07.750

3

Japt, 13 bytes

ö)g@¶°X?X+ß:X

Try it

Port of Arnauld's answer. Figured out how to make a recursive call ;)

Transpiled JS:

// U: Implicit input
// ö: generate a random number [0,U)
(U.ö())
  // g: run the result through a function
  .g(function(X, Y, Z) {
    // increment the result and compare to input
    return U === (++X)
      // if they are the same, roll again and add to current roll
      ? (X + rp())
      // if they are different, use current roll
      : X
   })

dana

Posted 2019-04-12T14:09:05.373

Reputation: 2 541

1Very nice use of N.g(f) :) – Shaggy – 2019-04-13T11:56:11.493

Took a stab at this meself and ended up with 12 bytes but I don't want to post it 'cause I like your solution too much! – Shaggy – 2019-04-13T19:15:48.237

Post it as a different answer :) – dana – 2019-04-13T19:19:46.783

It may be shorter, but it's a hell of a lot uglier than yours: https://petershaggynoble.github.io/Japt-Interpreter/?v=1.4.6&code=CvYKPrBWqVaqVivf&input=Ng

– Shaggy – 2019-04-13T19:21:19.403

I see - yeah I was trying to come up with a way not to pollute U. Skipping a line seems to work as well. That's a good trick :) – dana – 2019-04-13T19:37:30.580

When ETH first added support for multi-line programmes, starting a programme with an empty line used to be a lot more common. If I remember rightly, Oliver was particularly fond of that trick. – Shaggy – 2019-04-13T19:50:31.240

3

Japt, 12 bytes

It may be shorter than dana's solution, but it's a hell of a lot uglier. I'm only posting it 'cause it seems like forever since we had a Japt solution that started with an empty line.


ö
>°V©VªV+ß

Try it

Shaggy

Posted 2019-04-12T14:09:05.373

Reputation: 24 623

2

Python 3, 81 72 bytes

from random import*
def f(x,a=0):
 while a%x<1:a+=randint(1,x)
 return a

Try it online!

-9 bytes thanks to ArBo

Explanation

import random             #load the random module              
def explodeDice(num):     #main function
    ans = 0                     #set answer to 0
    while a % num != 0:         #while a isn't a multiple of the input
        ans += random.randint(1, num) #add the next dice roll to answer
    return ans                  #return the answer

Artemis still doesn't trust SE

Posted 2019-04-12T14:09:05.373

Reputation: 525

You can save 1 byte by using from random import* instead. – orthoplex – 2019-04-12T15:48:48.233

1

You can get this down to 74 bytes using this recursive solution

– Reinstate Monica – 2019-04-12T15:51:43.647

1

@squid You can save 1 byte like this.

– orthoplex – 2019-04-12T16:01:47.823

1

@orthoplex and then you can shorten the if/else, and make it a one-liner. Starts to look like my solution then ;)

– ArBo – 2019-04-12T16:16:31.680

1@ArBo Yea that's why I didn't change to recursive, didn't want to just copy you. – Artemis still doesn't trust SE – 2019-04-12T17:18:37.210

@squid [see above] ... should've said that, slipped my mind sorry – Artemis still doesn't trust SE – 2019-04-12T17:19:18.017

@ArtemisFowl Thanks for that... if you want, here's a non-recursive golf of your answer :)

– ArBo – 2019-04-12T17:26:44.240

@ArBo Added, explanation coming later, – Artemis still doesn't trust SE – 2019-04-12T17:48:58.470

2

JavaScript (ES6),  39  30 bytes

Saved 9 bytes thanks to @tsh!

f=n=>Math.random()*n|0||n+f(n)

Try it online! or See the distribution for n=4

Arnauld

Posted 2019-04-12T14:09:05.373

Reputation: 111 334

130 bytes, f=n=>Math.random()*n|0||n+f(n) – tsh – 2019-04-15T09:18:15.757

2

Forth (gforth), 72 bytes

include random.fs
: f >r 0 begin i random 1+ >r i + r> i < until rdrop ;

Try it online!

Code Explanation

include random.fs      \ include library file for random
: f                    \ start a new word definition
  >r                   \ stick the input on the return stack (for easy access)
  0                    \ add a counter to hold the sum
  begin                \ start an indefinite loop
    i random 1+        \ generate a random number from 1 to n
    >r i + r>          \ add the result to the counter, use the return stack to save a few bytes
    i <                \ check if result was less than n
  until                \ end the loop if it was, otherwise go back to begin
  rdrop                \ remove n from the return stack
;                      \ end the word definition

reffu

Posted 2019-04-12T14:09:05.373

Reputation: 1 361

2

TI-BASIC, 28 23 bytes

-5 bytes thanks to this meta post!

Ans→N:0:Repeat fPart(Ans/N:Ans+randInt(1,N:End:Ans

Input is in Ans.
Output is in Ans and is implicitly printed.

Examples:

4
              4
prgmCDGF11
              5
6
              6
prgmCDGF11
              3

Explanation:

Ans→N:0:Repeat fPart(Ans/N:Ans+randInt(1,N:End:Ans   ;full logic

Ans→N                                                ;store the input in "N"
      0                                              ;leave 0 in "Ans"
        Repeat fPart(Ans/N                 End       ;loop until the sum
                                                     ; is not a multiple of
                                                     ; the input
                               randInt(1,N           ;generate a random
                                                     ; integer in [1,N]
                           Ans+                      ;then add it to "Ans"
                                               Ans   ;leave the sum in "Ans"
                                                     ;implicitly print "Ans"

Notes:

  • TI-BASIC is a tokenized language. Character count does not equal byte count.

Tau

Posted 2019-04-12T14:09:05.373

Reputation: 1 935

Since startTmr is no longer necessary, this submission will now work for versions of TI-BASIC earlier than the TI-84+ – Tau – 2019-04-13T00:17:23.983

2

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

william porter

Posted 2019-04-12T14:09:05.373

Reputation: 331

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 – Taylor Scott – 2019-04-15T19:03:31.240

1@TaylorScott Thanks, I forgot that Do x While y existed in Excel VBA. – william porter – 2019-04-15T20:23:03.807

2

Haskell, 94 81 78 bytes

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

bugs

Posted 2019-04-12T14:09:05.373

Reputation: 211

81 bytes – dfeuer – 2019-04-13T02:13:08.113

Wuups, fixed the typo! And thanks for the suggestion :) – bugs – 2019-04-13T10:16:18.577

2

PowerShell, 49 bytes

for($a=$l="$args";$a-eq$l){$o+=$l=1..$a|Random}$o

Try it online!

Iterative method. Sets the input $args to $a and the $last roll (done so we enter the loop at least once). Then, so long as the last roll is -equal to the input, we keep rolling. Inside the loop we accumulate into $o the last roll, which is updated by creating a range from 1 to input $a and picking a Random element thereof. (Honestly, I'm a little surprised that $o+=$l= works.) Once we're out of the loop, we leave $o on the pipeline and output is implicit.

AdmBorkBork

Posted 2019-04-12T14:09:05.373

Reputation: 41 581

2

Batch, 70 bytes

@set t=0
:g
@set/at+=d=%random%%%%1+1
@if %d%==%1 goto g
@echo %t%

Takes input n as a command-line parameter %1. d is the current roll, t the cumulative total. Simply keeps rolling until d is not equal to n.

Neil

Posted 2019-04-12T14:09:05.373

Reputation: 95 035

2

Jelly, 9 bytes

x⁹X€Ä%ƇµḢ

Try it online!

A monadic link that takes n as its argument and returns a number generated by an exploding n-sided die. This generates 256 numbers from 1 to n and returns the first cumulative sum that is not a multiple of n. In theory this could return 256n, but even for a 2-sided die this would happen only one every \$2^{256}\$ times.

An alternative that doesn’t have this limitation is:

Jelly, 10 bytes

X³X¤+¥³ḍ¥¿

Try it online!

Note both TIO links generate 400 numbers to show the distribution.

Nick Kennedy

Posted 2019-04-12T14:09:05.373

Reputation: 11 829

2

SmileBASIC 3, 49 bytes

Function D N OUT R implements exploding dice rolls recursively.

DEF D N OUT R
R=RND(N)+1IF R==N THEN R=R+D(N)
END

Ungolfed

DEF D N OUT R  'N is sides and R is output param (shorter than using RETURN in this case)
 R=RND(N)+1  'random number in [1, N]
 IF R==N THEN R=R+D(N)  'if roll is same as N then roll again and add
END

Note that in SmileBASIC, functions can have multiple return values. If a function has one return value then fun in OUT var and var = fun(in) are exactly the same, which is why we can define the function in OUT form and also call it in an expression in the function body itself. If I had defined the function as DEF D(N) I would have to explicitly state RETURN R in the function body; mixing both syntaxes saved me bytes.

snail_

Posted 2019-04-12T14:09:05.373

Reputation: 1 982

2

PowerShell, 43 bytes (Iterative method)

param($n)do{$x+=1..$n|random}until($x%$n)$x

Try it online!


PowerShell, 48 bytes (recursive method)

filter f{if($_-eq($x=1..$_|random)){$x+=$_|f}$x}

Try it online!

mazzy

Posted 2019-04-12T14:09:05.373

Reputation: 4 832

2

Jelly, 7 bytes

X=п⁸S_

A monadic Link accepting an integer, n, which yields an integer.

Try it online! Or see the counts of \$10^5\$ runs

How?

X=п⁸S_ - Link: integer, n
  п    - Collect up while...
 =  ⁸   - ...condition: equal to chain's left argument, n
X       - ...next value: random number in [1..n]
     S  - sum
      _ - subtract n (since the collection starts with [n])

Jonathan Allan

Posted 2019-04-12T14:09:05.373

Reputation: 67 804

2

SmileBASIC, 41 bytes

INPUT N@L
S=S+RND(N)+1ON S MOD N GOTO@L?S

After reading:

Note that you should never output any multiples of n, since they will always explode.

I realized that rather than checking if a dice roll was n, you can just repeat while the sum is a multiple of n.

12Me21

Posted 2019-04-12T14:09:05.373

Reputation: 6 110

2

AnyDice, 36 bytes

Almost a built-in in the language:

function:f I:n{result: [explode dI]}

For this to be correct I have to abuse the infinite recursion depth assumption. AnyDice limits the recursion depth with a global property maximum function depth. the explode builtin however uses it's own; explode depth - which defaults to 2.

set "explode depth" to 99

Would add another 25 bytes; and would not really match the requirements since it's theoretically possible for a dice to explode more than 99 times.

The output of the function is a die, ie. an AnyDice built-in type that is a paring of results and probabilities for the result.

Taemyr

Posted 2019-04-12T14:09:05.373

Reputation: 123

1I think I'm OK with it not exploding much, the 36 byte one is fine by me. I didn't say no builtins and I'm ok with having them here, since it's not like your 1 or 0 byte answer is winning. But welcome to the site! – Rɪᴋᴇʀ – 2019-04-15T13:57:20.380

2

CJam, 19 bytes

qi{__mr)_T+:T;=}g;T

Explanation:

T is pre-set to 0

qi{__mr)_T+:T;=}g;T - whole code
qi                  - read input as integer (n) | Stack: n
  {            }    - block
   __               - Duplicate twice | Stack: n n n
     mr)            - Choose a random number from 1 to n (r). Since 'mr' picks a
                      number from 0 to n-1, the number has to be incremented with ')' 
                      Stack: n n r
        _           - Duplicate |  Stack: n n r r
         T          - push T | Stack: n n r r T
          +         - add the random number to T (t) | Stack: n n r t
           :T;      - pop the value and store in T | Stack: n n r
              =     - are the top two stack values the same (c) | Stack: n c
               }
                g   - do while loop that pops the condition from the stack after each
                      iteration | Stack: n
                 ;  - pop the top stack element and discard | Stack: T
                  T - push T | Stack: T
                    - implicit output

Or in pseudocode:

input n
var sum = 0
do {
    var random_number = pick random number from 1 to n
    sum = sum + random_number
} while (random_number == n)
output n

As a flowchart:

Flowchart of code

Try it online!

lolad

Posted 2019-04-12T14:09:05.373

Reputation: 754

2

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.

Sophia Lechner

Posted 2019-04-12T14:09:05.373

Reputation: 1 200

2

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).

Luis Mendo

Posted 2019-04-12T14:09:05.373

Reputation: 87 464

1

><>, 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.

Conor O'Brien

Posted 2019-04-12T14:09:05.373

Reputation: 36 228

Compression to 72 bytes. – Jo King – 2019-04-13T00:07:15.137

1

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

Giacomo Garabello

Posted 2019-04-12T14:09:05.373

Reputation: 1 419

1Shouldn't srand(time(0)) be included in the byte count? AFAIK rand() will always return the same value if it was never seeded – Tau – 2019-04-12T16:42:46.760

1I'm not entirely sure, but I remember the meta consensus being that using an unseed PRNG is acceptable @Tau – Conor O'Brien – 2019-04-12T16:50:15.210

@ConorO'Brien i was looking on the meta and i found this: https://codegolf.meta.stackexchange.com/questions/15025/what-are-the-standard-requirements-for-answering-a-random-challenge

– Giacomo Garabello – 2019-04-12T16:52:28.533

@GiacomoGarabello I'm 100% ok with a non-seeded PRNG, and it's allowed by our rules on randomness I believe. – Rɪᴋᴇʀ – 2019-04-12T22:51:50.613

1This is not even close to being C code. Your recursive call has the wrong number of parameters, and you are missing a return. – Ben Voigt – 2019-04-13T18:46:45.807

Also, +1 and comparison to n aren't needed, this is perfectly valid: f(n){int x=rand()%n;return x?x:n+f(n);} – Ben Voigt – 2019-04-13T18:56:13.430

@BenVoigt that is the exact reason why I love to golf in C. If you try with your GCC compiler (or the TIO link) you can see it works correctly! Also, thanks for the tip! – Giacomo Garabello – 2019-04-15T07:19:15.823

1Even 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. – Ben Voigt – 2019-04-15T14:32:01.747

1@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. – Skidsdev – 2019-04-15T14:55:49.593

1

Attache, 30 bytes

f:=${If[x=y,f@x+y,y]}#1&Random

Try it online!

Direct implementation of the process.

Alternatives

36 bytes: ${NestWhile[{_+Random[1,x]},x&`|,0]}

37 bytes: ${NestWhile[{_+Random[1,x]},{x|_},0]}

Conor O'Brien

Posted 2019-04-12T14:09:05.373

Reputation: 36 228

1

Perl 6, 26 bytes

{[+] {^$_ .roll+1}...$_>*}

Try it online!

bb94

Posted 2019-04-12T14:09:05.373

Reputation: 1 831

1

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

Delya Erricson

Posted 2019-04-12T14:09:05.373

Reputation: 81

I don’t think python does tail-call optimization, does it? Regardless, good answer anyways! – JPeroutek – 2019-04-15T17:21:09.780

Unfortunately not. I've heard that Guido doesn't want python to turn into a functional language – Delya Erricson – 2019-04-15T17:23:16.100

1

Charcoal, 17 bytes

NθW⁼Lυ№υθ⊞υ⊕‽θIΣυ

Try it online! Link is to verbose version of code. Explanation:

Nθ

Input n.

W⁼Lυ№υθ

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.

IΣυ

Print the total.

Neil

Posted 2019-04-12T14:09:05.373

Reputation: 95 035

1

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!

Embodiment of Ignorance

Posted 2019-04-12T14:09:05.373

Reputation: 7 014

60 bytes – Expired Data – 2019-04-12T19:48:51.363

@ExpiredData Using new Random() gives it the same seed if you call it again, which doesn't make it truly random – Embodiment of Ignorance – 2019-04-12T20:11:39.007

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.... – Expired Data – 2019-04-12T20:40:52.993

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 – 2019-04-13T05:38:28.380

1This 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 – 2019-04-13T05:42:29.207

1

Perl 6, 25 bytes

{sum {.rand+|0+1}...$_>*}

Try it online!

Jo King

Posted 2019-04-12T14:09:05.373

Reputation: 38 234

1

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.

Ciaran_McCarthy

Posted 2019-04-12T14:09:05.373

Reputation: 689

You're implementing so many extra things. They don't appear to be in the code you're counting, though, so...okay. – Stackstuck – 2019-04-17T08:18:38.240

What do you mean by extra things? – Ciaran_McCarthy – 2019-04-17T20:04:53.827

"shows ...the number of times it has been rolled", not a requirement. – Stackstuck – 2019-04-17T21:14:11.927

..You need to roll one exploding die. Not 10,000. I'm not sure this is valid...? – Stackstuck – 2019-04-17T21:22:09.247

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. – Ciaran_McCarthy – 2019-04-18T21:34:44.870

1

Ruby, 28 bytes

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

Try it online!

G B

Posted 2019-04-12T14:09:05.373

Reputation: 11 099

1

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!

Olivier Grégoire

Posted 2019-04-12T14:09:05.373

Reputation: 10 647

1

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.

Stackstuck

Posted 2019-04-12T14:09:05.373

Reputation: 209

Saved 2 bytes by using [0,i)+1 rather than [1,i+1) – Stackstuck – 2019-04-17T19:35:55.833

Saved 8 bytes by forgoing principles about RNG and just making a new one in the loop. – Stackstuck – 2019-04-17T21:19:15.797

Saved one byte w/ <1 instead of ==0 – Stackstuck – 2019-04-20T07:35:11.583

1

Funky, 38 bytes

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

Try it online!

ATaco

Posted 2019-04-12T14:09:05.373

Reputation: 7 898

1

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)))))

clismique

Posted 2019-04-12T14:09:05.373

Reputation: 6 600

1

VTL-2, 54 53 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.

brhfl

Posted 2019-04-12T14:09:05.373

Reputation: 1 291

Is this the language from the Altair systems? Google seems to imply so. But this is really cool! – Rɪᴋᴇʀ – 2019-05-04T20:55:23.207

@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!). – brhfl – 2019-05-04T21:29:06.417

1Huh, that's pretty cool. I'm glad you decided to answer my challenge in that language, of all things. – Rɪᴋᴇʀ – 2019-05-04T23:38:02.310