Uncle Sean's Electronix Fun Page
Home
Contact
Links
Legal

Random Number Generator

Of necessity, the random number generator in One Chip Poker is very simple. It's the old standard Linear Congruential Random Number Generator (LCRNG) described in Numerical Recipes in C, and which can be found all over the place online. There's a good brief discussion of it at Wikipedia.

Basically, how it works is like this:

  1. Start with a 32-bit value, n.

  2. Every time you want a new "random" number, set
    n = (1664525 * n) + 1013904223.
    Because we're keeping it in a 32-bit container, there's an implicit modulo 232 you get “for free”.

  3. now n is your new random number.

In general, the higher-order bits of LCRNG samples are more random than the low-order bits, so I use the high-order bits in the deal/draw code.

Stirring

One big drawback with LCRNGs is that they are just a repeating series of numbers which look sort of random - even if it is a long series, more than 4 billion numbers in this case. There's a good old video-game-biz trick for helping out with this, which is to throw in a real-world randomizing factor: user response time. How does that work? Well, in human terms, this LCRNG implementation is pretty fast. I estimate that it takes about 400 instruction cycles to run. If you're using the internal 16F628 oscillator at 4MHz (1MHz instruction cycle), it takes about 0.4 milliseconds per iteration. Human reaction times in button pressing tend to be in the tenth-of-a-second range, so even if a user is twiddling a button as fast as they can this thing can step through a whole bunch of iterations - even calling it 50 times a second is fast enough, which is the same rate as the button-debouncing interval used in One Chip Poker's button input routine. If you just keep calling the RNG while waiting for user button presses, it "stirs" the RNG nicely by more randomly sampling the LCRNG sequence.

However: Looking ahead to what we'll really have in the game, it's going to go something like this.

  • Player turns on the power.
  • Splash screen shows up, inviting the user to press the Deal button to start. This is so we can do an initial stir.
  • Player has seen this before so immediately mashes the Deal button to get rolling.
  • Likewise with the bet screen.

So, that didn't give us much of a chance to stir; if the seed is hardcoded to start at the same value on reset, and the RNG is stirred once per debouncing interval, chances are that fewer than ten stirs will have happened - resulting in only about ten initial hands. We can't stir and sample the buttons much faster or the debouncing might not work. Calling the LCRNG a fixed but larger number of times per button sampling period doesn't help either because there will still only be about ten different RNG states once the game starts.

One solution is to avoid having the same seed every time at startup. Thus, it can't be hardcoded; it has to be stored somewhere. Happily, the PIC 16F628 has a built-in mechanism for just such an application - the Data EEPROM. Like the program Flash-ROM, the Data EEPROM is memory that will retain its contents even while the circuit's power is off. Think of it as a 128-byte hard drive.

So, all we have to do is write the current seed into Data EEPROM once in a while, and read it out of there on powerup.

LCRNG Unit Test

Here's a screenshot of a little test that shows the first 8 numbers output by the PIC version, starting with the seed 0x1337D00D, and the expected values as printed out by a little PC console program implementing the LCRNG:

8 LCRNG numbers

Actual command prompt screenshot!

Pressing a button advances to the next 8 numbers, which can be compared to the next 8 from the PC, for as long as it takes to convince you that the PIC version is working.