One problem with my approach is that patterns are zero-indexed: the first slot is slot 0, the second is slot 1, and so on. There’s a fix, but let me break the whole thing down line by line.
T RRAND 0 PN.L 3; X PN 3 T
This puts our random number in T and puts the pattern value at the corresponding slot into X. The range on RRAND here is from 0 to the current length of the pattern, inclusive, but recall that our patterns are zero-indexed; if we happen to roll the maximum value here (the pattern length), we’ll be trying to get a pattern slot that’s actually one position past the end of the pattern. My bad! I’ll give a fixed line when we’re done.
Anyway, the second clause here after the semicolon is putting the value that’s currently at slot T in the pattern into X. The pattern only contains numbers we haven’t picked out already this round, so this is the actual return value we want. The next step is to remove the value we just picked from the pattern so we don’t pick it again until after a refill:
PN.RM 3 T
So that’s done, removing the value and pulling the rest of the pattern up to fill the empty space, thereby changing the pattern length. If the pattern still has any length, that’s all we want to do here, so it’s time for my favorite trick, the conditional break:
IF PN.L 3: BREAK
PN.L 3 will be zero if we’re out of values; if it’s anything else, the BREAK will execute and we exit the script. If we’re still here by line 4, though, the pattern must be empty, so it’s time to refill.
L 0 15: PN.PUSH 3 I
This loops through its bounds (inclusive of both of them, just like RRAND), putting the loop number into I, and pushing I onto the pattern, automagically extending the pattern length to fit. But here’s what got me: PUSH doesn’t take a position as an argument - it doesn’t need to. It just pushes onto the end, it’s in the name. So I didn’t need to zero-index this loop at all. We could just add 1 to I and fix our delivered values to properly run from 1 to 16, we’ve got room on the line after all… but running the loop starting at zero is totally unnecessary. (As is zero indexing, by the way; computers don’t get any real advantage from it anymore, it’s just an onion in the varnish. Alas, we’re probably stuck with it.)
Here’s the complete script with fixes.
T RRAND 0 - 1 PN.L 3; X PN 3 T
PN.RM 3 T
IF PN.L 3: BREAK
L 1 16: PN.PUSH 3 I
Line 1 is a tight squeeze, but still fits. Phew!