The Challenge of Randomness
CPUs are deterministic: they do the same thing every time. To generate a truly unpredictable sequence, we need a source of entropy (unpredictable noise) from the outside world.
Method 1: Hardware Noise (Simple Seed)
The fastest way to get a simple random number is to read a register or port whose value is constantly changing due to external factors.
Noise Sources:
- I/O Port Reading: Reading from an unused or floating I/O port can return unpredictable bits of electrical noise.
- The Refresh Register (R): As discussed earlier, the
R
register increments automatically, making its low bits somewhat unpredictable depending on when it’s read. - The Keyboard: Polling the keyboard port and using the lower bits of the input byte before any key is pressed.
Example: Reading an Unpredictable Port
RANDOM_PORT EQU 0FFH ; Example: An unused I/O port address
GET_SEED:
IN A, (RANDOM_PORT) ; Read the unpredictable noise into A
AND 3FH ; Mask off the high bits to get a smaller range (0-63)
LD RANDOM_SEED, A ; Store A as our initial 'seed'
RET
Method 2: The Linear Congruential Generator (LCG)
Once you have a starting point (the seed), you can use an algorithm like the Linear Congruential Generator (LCG) to produce a long sequence of numbers that appear random.
The LCG Formula: $$\text{Next Value} = (\text{Current Value} \times A + C) \bmod M$$ Where:
- $A$ = Multiplier (A carefully chosen constant, e.g., $101$).
- $C$ = Increment (A constant, e.g., $1$).
- $M$ = Modulus (Determines the sequence length, e.g., $256$).
Z80 Implementation Focus:
The Z80 code focuses on performing the 8-bit multiplication and addition, using the Carry Flag (ADC
) and bitwise masking (AND
) to manage the result within the 8-bit or 16-bit boundaries.
Advantage: LCGs are fast, repeatable (good for debugging), and produce sequences long enough for most 8-bit games.
Practical Usage (Limiting the Range)
Once you have a random byte in the Accumulator (A), you usually need to limit it to a small range (e.g., 0 to 5) for game logic.
Limiting Range: To get a random number between 0 and N:
- Perform the LCG generation.
- Divide the result by a constant M and use the remainder (the modulo operation).
- In Z80, the simplest way is often repeated subtraction or repeated shifting and masking.