The Illusion of Randomness: Making Dice Games "Overly Fair"
Have you ever played a board game where the dice just seemed to hate you? You need a 6 to win, but you roll 1, 1, 2, 3, 1, 2... It feels unfair. And technically, in a truly random universe, it is possible to never roll a 6 in your entire life (though statistically unlikely).
True randomness is cruel. It has no memory. It doesn't care that you haven't rolled a 6 in twenty turns.
But what if we could fix that? What if we could make a dice game that guarantees fairness, ensuring everyone gets their fair share of luck?
Enter the Wurfel page, a new addition to this site that explores the concept of "Bag Randomness".
The Problem with Math.random()
Most digital dice games use a pseudo-random number generator (PRNG) like Math.random() in JavaScript. It picks a number between 0 and 1, and we scale it to 1-6.
const roll = Math.floor(Math.random() * 6) + 1;
This is fine for most things. But as mentioned, it allows for "streaks". You can roll five 6s in a row, or none at all for a hundred turns. For a competitive game, this variance can be frustrating.
The "Bag of 6" Solution
To solve this, I implemented a sampling-without-replacement algorithm, often called a "Bag System" in game design (famously used in Tetris to determine the next piece).
Imagine a physical bag containing exactly six marbles, numbered 1 through 6.
- You shake the bag.
- You pull out one marble. That's your roll.
- You keep the marble out of the bag.
- Repeat until the bag is empty.
- Once empty, put all six marbles back in and start over.
This simple change has a profound effect on the probability distribution.
Why it feels "Fairer"
With this system, in any given sequence of 6 rolls, you are guaranteed to see every number exactly once.
- If you haven't rolled a 6 in the last 5 turns, your next roll is 100% guaranteed to be a 6.
- You can never roll the same number more than twice in a row (e.g., the last marble of bag A and the first marble of bag B).
This reduces variance significantly. It rewards patience. If you are having a bad run, you know for a fact that a good run is just around the corner.
The Code
Here is the core logic directly from the Wurfel component source:
roll(player: Player) {
// If the bag is empty, refill and shuffle
if (player.bag.length === 0) {
player.bag = this.shuffle([1, 2, 3, 4, 5, 6]);
}
// Pop a number from the bag
const result = player.bag.pop();
if (result !== undefined) {
player.lastRoll = result;
player.history.push(result);
// ... logic to check for wins
}
}
Try it out!
Head over to the Wurfel page and try it yourself. Add a few players and watch the distribution. It’s a fascinating look at how tweaking randomness can change the "feel" of a game.
Is it cheating? Maybe. Is it better? That's for you to decide. But one thing is certain: it's definitely fair.