Better Than Dice: Stats for RPGs.

Do you use random numbers in your game? Then you're probably using them in a naive way! Read this article and learn about the psychology of how humans perceive random numbers. Armed with this knowledge you'll be able to make better design decisions and better games. Being able to determine what is random, and what is not, is subtle and difficult; so our brains use a number of shortcuts. If you know about these shortcuts, you can make better, less frustrating games!

People replace the laws of chance [with] heuristics, which sometimes yield reasonable estimates and quite often do not.

  • Subjective Probability: A Judgment of Representativeness, Kahneman & Tversky, 1972

These shortcuts (or heuristics) have been studied and as a game designer it's worth having a passing knowledge of the rules. In this article we'll mostly consider sequences of coin tosses as much of game design can be reduced to this kind of sequence.

Judging a Sequence to be Random

Let's say Bob has a coin and when he flips it, it either lands heads, H, or tails T. If Bob flips his coin six times he'll end up with a sequence of heads and tails. Now if Bob asks Alice which of two sequences HTHHTT or HHHHHH, was created by flipping the coin and which was just made up, Alice will choose HTHHTT. But as any statistician knows, both orders are equally likely. Each coin flip has a 50% chance of being heads and 50% of being tails, no flip influences any future flip but because of the heuristics we all use we can't help but consider HTHHTT as the more random.

We expect a series of random numbers to have these properties:

  1. Irregular appearance order (e.g., HHTHTH vs. HTHTHT).
  2. Both heads and tails are expected to occur roughly equally in the sequence.
  3. Overalternation Bias: The outcome alternation rate (i.e., how often H switches to T and vice versa). People consider a sequence random, if the alternation rate is higher than that associated with chance.
  4. Gambler's Fallacy: after a long streak of heads, there's greater expectation of a tails. (In truth either outcome is equally likely.)

When Chance and Heuristic Meet

When a player plays a game with random numbers they use these heuristics to form expectations about the future. If their space marine sniper misses a xenomorph three times in a row; there's an expectation the next shot is far more likely to hit. Unfortunately with random chance this expectation is baseless. It's likely the player's expectations are not going to be met. Continually failing to meet expectations gives rise to feelings of frustration.

At this point, a engineering-minded reader might be feeling the following:

"These people just need to buck up! They need to learn how true random works, that's how the world works! They can't expect the world to change for them!".

To this I'd say: "Player's expectations about randomness aren't conscious; they're deep seated biases programmed by millions of years of evolution. It's unrealistic to expect someone to change themselves for your game. A player wants to have fun, it's best to accept random-number biases exist and develop for them."

If you're expecting a reward and you don't get it, dopamine levels fall steeply. This feeling is not a pleasant one, it feels a lot like pain. [...] You are left feeling frustrated.

Ideally we want to minimize player frustration but not totally avoid it. Luckily there's a solution! We can change our systems to use random numbers in a way that's less frustrating for the player. This almost sounds impossible, right? But it's true.

Reducing Frustration by Designing to Bias

Let's say we're determining if an attack hits or misses. To make the numbers easy to model we'll have a 1 in 6 chance that an the attack misses (~83% chance to hit). First we'll consider how to model this using "true random" numbers.

Note: I use the term random in the article but unless you have special hardware then the random numbers from your computer are actually pseudo-random (Usually something like the excellently named [Mersenne Twister). This doesn't matter at all for us but it's interesting to note.

Normal Random Number Using Dice

We can represent the hit/miss chance with a dice roll, if the player rolls a 1 they miss if they roll anything else they hit. A dice roll is true random, each face is equally likely.

Rolling a 1D6.

If we do the attack 24 times we might get a sequence like this, where the 1 is a hit, the _ a miss:

111111111111111111_1_1_1_

This is a totally random string I've just generated. Look at this string and try to interpret how the player might feel. At the start, they've got an 83% chance to hit so they're thinking "Yeh, this is going well, pretty much unstoppable". When they get that first miss they probably accept it; you can't hit everytime. But then after each following hit they miss - that's seen as a streak of bad luck and if it keeps happening can be frustrating. If I run the program a couple more times I can get sequences like this:

1111___111111111111_1_111

In this sequence after the player hits four times, they miss three times, if this is an important attack this kind of bad luck could ruin the whole battle! The player won't blame themselves for not having a good model of real random numbers; they'll just become frustrated and increasingly want to quit. They may even question how the game is programmed - "Three misses on an 83% chance to hit? This is impossible" or something to that effect.

Here's a code snippet for getting a random 1D6 value.

value = math.random(6) -- returns a random int from 1 to 6 inclusive.
is_hit = value ~= 1

When to use?

True random can be addictive and fun, just look to Las Vegas or your nearest Magic: The Gathering, booster pack enthusiast.

Here's some tips about when it's appropiate to use true random:

  • User expects failure case to be common
  • Number is not exposed to player
  • Rare chances events; rare loot drops, rare creature encounters etc.
  • Critical attacks

It number represents the chance of rare event or the chance of something percieved to be outside of the users control, then true random works well.

When to Avoid?

Avoid when:

  • The worse case seems particularly frustrating
  • This one event could make or break for the player
  • The chance is representing a player skill
  • If the chance fails the player is heavily punished; waiting a long time to try again or losing the game and having to restart.

For instance missing an attack in a game like X-Com sucks because there's such a long time lag until the next chance to try again. The worse case for true random is missing everytime! Which is unlikely but possible.

Restricted Random Using Cards

Instead of dice rolls what if we instead had a deck of cards. 1 card is a miss card, the other five cards are hits. We then shuffle this deck and draw cards from the top. When we run out cards we recreate the deck and shuffle again (we also move the top card to the back of the deck if it was the same as the one previously drawn). In this way we guaranteed to get each to get one miss every six attacks, we just don't know when it will occur.

Picking from 1D6 cards.

If you run this type of sequence a few times you get the following types of output

111_1111_111111_111111_1
11_1111111_111_11111_111
1_11111_11111_111111111_
11111_11_11111111_111_11
11_111_111111111_111111_
1111_111_111_1111111_111
1_11111_11111111_1111_11
11111_11111_1111_11_1111

Note that you never get two misses in a row with this system. The distribution of misses is more even and predictable. This how the brain thinks random numbers should work.

When using this type of random you can make statements like: if I draw six cards, I am guaranteed to get a six.

The code for this system need has more overhead but is basically something like this:

function shuffle(t)
    local n = #t
    while n > 2 do
        local k = math.random(n)
        t[n], t[k] = t[k], t[n]
        n = n - 1
    end
    return t
end

t = shuffle({1,2,3,4,5,6})
index = 1

value = t[index]
index = index + 1

-- Code to reshuffle deck if we run out of cards
if index > #t then
    shuffle(t)
    index = 1

    -- Check the top of the deck doesn't equal the value we just returned.
    if t[1] == value then
        t[1], t[#t] = t[#t], t[1] -- put top card to the back of the deck
    end
end

is_hit = value ~= 1

When to Use

Choose to use the deck of cards rather than dice rolls when:

  • Numbers are seen directly be the player.
  • Numbers are important to the player.
  • It is important that random number seem "fair".
  • A string of poor random results may cause frustration.
  • You want to gurantee a certain distribution (as this may make game mechanics easier to reason about)

Specifically you may want to use these numbers for determining hit / miss outcomes, fleeing and steal success chances.

When to Avoid

Avoid cards if can be easily exploited by the player and break the game. Mutliplayer games should take extreme care when using this system. The player can count the cards and exploit the system, leading to a unfair advantage.

Cards and Dice Aren't the End

There are many ways of generating semi-random numbers. But cards and dice are easy to visualise. For instance you might have a likely hood counter for a miss that's reset to 0 each time one occurs and then slowly becomes more and more likely with subsequent attacks.

A Concrete Example: Random Encounters

In the How to Make an RPG book a random encounter system is implemented on the world map. In the book this is a true random system everytime the player takes a step they have a small chance of being attacked. To prevent two successive random encounters only half the tiles have a chance to trigger an encounter. You can see the checkerboard pattern below. Only the color squares can trigger an encounter.

But this system still has the potential to frustrate. It would be better to use the card type picking. In this system the player might leave one battle, get forced into another, only to leave that and forced into yet another! Random encounter systems are frustrating at the best of times but getting a streak of attacks just plain isn't fun!

In the book we get around this by only checking for combat on every other tile; guaranteeing you'll never have a run of two battles. The encounter tiles appear like a checkerboard pattern as in the image below. The pale tiles represent areas where there's a chance of an encounter.

Encounter data on the world map.

A better method for handling random encounters is the deck of cards approach. The deck would contain a single encounter card and five or so safe cards. Each time the player takes a step we check the top encounter card before discarding it. This way we have a decent guarantee that the player won't get overwhelmed with frustrating random combat.

If you own How to Make an RPG book, then this a good challenge - try rewriting the encounter code, test both versions and see which you prefer!

Take Aways

Do you use random numbers in your game? Might those numbers frustrate your player? If so, you now have a new system you can use to make your game feel better. Your player will be happier playing the game and feel it's fairer because it more closely corresponds to their natural intuition. Consider your own game - which random system would work best?

Good luck!