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.

  1. You shake the bag.
  2. You pull out one marble. That's your roll.
  3. You keep the marble out of the bag.
  4. Repeat until the bag is empty.
  5. 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.