><>, 50 bytes
Try it online!
After many iterations, I got down to this version. Pretty happy with it, although I will admit I'm not convinced it's optimal.
The ><> language doesn't have a natural builtin for generating a random number in a range. The only source of randomness we have is the
x command, which sends the IP in a random direction. This gives us 1 of 4 options, which doesn't immediately fit into the range 0-99. However, we can easily use this command to generate a random bit, 0 or 1, like so:
Going in a left-to-right direction, the IP can either be sent leftwards back to the
>, where it tries again, upwards, wrapping around to try again, rightwards to
1, or downwards to
0. This uniformly generates a random bit, although perhaps it can be made shorter by making that not so.
We're going to loop 7 times each time we generate a random number, for 7 bits. This will give us the binary for numbers from 0 to 127—more than we need, but we'll fix that later. Instead of collecting all the bits and subsequently converting that to a decimal number, we'll do both steps at once.
701 initializes the stack with our configuration.
7 is the loop count,
0 is the running sum, and
1 is the multiplicative factor.
$:2* will make a copy of the multiplicative factor and double that copy for the next iteration.
}*+ will multiply the generated bit by the multiplicative factor and add it to the sum.
}1-:?!v will decrement the loop counter, and if it's 0, stop looping.
}30., in the event we don't stop, will reorganize the stack for the next iteration and jump back to the beginning (past the initialization).
The second line mostly contains the outer loop. It is written "backwards" to save space. First,
r will place the generated sum on the top of the stack.
%*aa will take that value mod 100, giving us a value from 0 to 99, albeit not uniformly. Then, we simply output that number followed by a newline (
oan:). After that,
;?=0 terminates the program if 0.
We then have a single
. command, which pops
x off the stack and jumps to that location in the code. Since the stack contains
[0, 64] at this point (the counter and the multiplicative factor, respectively), this jumps to (0, 64). Of course, this position doesn't exist in the code. In the Python implementation (the standard one), the interpreter will first increment this by the delta, which, at the time of execution, is (-1, 0). So, the IP is then (-1, 64) internally. To resolve this, ><> will first fix the y-coordinate, wrapping it around to 0 since it exceeds the bound, giving (-1, 0). When x < 0, it gets wrapped around to the end of the line, hopping to (28, 0), which is the
> command. This puts us back on the right direction and back at the beginning of the line.
Catalogue of Attempts
103 bytes: My first attempt. Completely ungolfed. First collects the bits, then converts it to a decimal in a separate stage.
! >:0=?v:'d'(?v >0[700.
;n< >n' 'o^
105 bytes: Post calculating the sum while collecting bits. Longer, but the structure offers us more opportunities to golf.
^\0^ >r:0=?v:'d'(?v >0[700.
;n< >n' 'o^
90 bytes: Introducing the modulus trick, rather than retrying if the number is 100 (
'd') or more.
64 bytes: Inlining the terminate-if-zero condition.
54 bytes: Moving the modulo calculation into the main loop so we have enough room to flip the second line and tuck it into the whitespace we have.
53 bytes: Since we have spare whitespace left, we can use it to set the pointer in the right direction to save the
> off the first line. Since that means we're jumping to the beginning of the line, we'd naturally skip
7, so we have to duplicate it before we jump.
This approach, however, left me at a dead end, so I tried putting the modulo calculation back into the second part, which leaves us with what we currently have.