Fair Dice on a Machine That Can't Roll Them
Backgammon is a running argument with the dice, and the whole thing collapses if the dice are crooked. Which is a problem, because a computer cannot roll anything. It’s a deterministic machine: same inputs, same outputs, forever. Ask it for a surprise and it has none to give.
So you fake it. Take a seed, run it through an arithmetic recurrence, and read off the low-order noise as though it were fortune. The oldest workhorse is the linear congruential generator: Xₙ₊₁ = (a·Xₙ + c) mod m. One multiply, one add, one modulo, and the numbers march off looking scattered.
let state = 42; // seed = a reproducible game
const next = () => (state = (1664525 * state + 1013904223) >>> 0);
const die = () => (next() % 6) + 1; // 1..6
for (let turn = 0; turn < 5; turn++) console.log(`roll ${die()}-${die()}`);
The constants are from Numerical Recipes, and m is 2³² — that’s what the >>> 0 enforces, wrapping the multiply back into an unsigned 32-bit box. Run it and you get 2-5, 6-1, 4-5, 6-5, 6-3.
Same recurrence in OCaml, where land 0xFFFFFFFF does the masking the shift did above:
let state = ref 42
let next () =
state := (1664525 * !state + 1013904223) land 0xFFFFFFFF;
!state
let die () = (next () mod 6) + 1
let () =
for _ = 1 to 5 do
let a = die () in let b = die () in
Printf.printf "roll %d-%d\n" a b
done
Seed it with 42 and it emits 2-5, 6-1, 4-5… the identical stream. That reproducibility is the useful part: a saved seed replays an entire match, dispute and all, blot for blot.
The constants are not decoration. IBM’s infamous RANDU shipped with poison values in the 1960s, and its “random” points all fell onto fifteen planes — visible the moment you plotted them in 3D. And an LCG is guessable: watch a few outputs and you can solve for the state, then predict every roll to come. For a friendly game across a kitchen table, none of that matters. Feed it to a cryptographic key and you’ve handed the attacker the whole board.