Between 2008 and early 2010, we must have played around 500 hours of the balloon battle mode of Super Mario Kart (the GameCube version) at the bobblebrook office. We knew the ins & outs of every stage, discovered little-known bugs/features (such as the ability to shoot across low walls and plateaus if you’re standing right in front of them – total game changer on the cookie stage), we tried playing the game in reverse gear only, and on the GameCube stage we even tried playing with closed eyes (which didn’t work that well, in case you’re wondering).
When you play a game a lot, small faults become very noticeable. With Mario Kart, our biggest ire was with the random number generator that determined which weapon a player would get when they’d hit an item box. You knew it was Monday when that damn thing gave one player one red shell after another while giving the other player nothing but bananas. We were wondering how such an obviously buggy piece of code made it into such a high profile game.
Except that Mario Kart’s RNG probably wasn’t buggy, at least not from a technical perspective.
Randomness doesn’t look random
The amazing radio show Radiolab had an episode on stochasticity which gave a great example for how unintuitive randomness can be: A professor would assign his students the task of either recording the results of a hundred coin tosses, or producing fake results with a hundred made up tosses. In every single instance, he was able to determine which results were fake and which were real.
It turns out that in a hundred coin tosses, the chance of one side of the coin coming up seven times in a row is over 50 percent. But, given the task of producing 100 fake coin tosses, how many developers would actually write down six or seven tails in a row (or even four or five)?
If I remember correctly, we played 18 rounds per game session of Mario Kart, with one session per workday. If each round has maybe a couple dozen item pickups, at over 1000 item pickups per week, the chances of a long series of red shells on one side and useless junk on the other add up.
Truly random behavior isn’t always perceived as random, and some behaviors that we perceive as random are heavily skewed. Different ways of introducing randomness can heavily influence how balanced your game will appear to players. They can also make your life easier or harder when tweaking your gameplay parameters.
A look at “Drifts”
Take our game “Drifts” as an example…
Drifts started out as a simple little Flash game in 2005 or so and was then ported across subsequent Flash versions and to Objective-C/cocos2D, and I have to admit that I carried its naive approach to randomness from port to port in almost every aspect of the game.
I wouldn’t say the result is a flawed game (it’s actually pretty swell – check it out!). It did however make the balancing of the parameters a lot harder and more time intensive than it would have been if we had employed a better scheme from the start, it resulted in a sub-optimal overall difficulty progression, and it resulted in your optimum game round being much more dependent on luck than it would have been necessary.
Maybe somewhere in the world, someone who is approaching their 500th hour of Drifts is shaking their fist at our RNG right now.
Quick overview of Drifts’ game rules – skip this paragraph if you know the game:
You steer an orange bubble, picking up green bubbles that float across the screen. Collisions with purple bubbles cost you a life (including collisions between purples and any greens attached to your bubble). Blue bubbles turn your attached green bubbles into points. Cashing in larger amounts of attached green bubbles at once exponentially increases the points you get. This creates a classic risk/reward dynamic, as attached bubbles increase your odds of hitting a deadly purple. Finally, occasional yellow bubbles provide bonus items, such as extra lifes or score boosts.
Drifts’ random numbers setup
When new bubbles are spawned in Drifts, a random number determines which bubble type it’s going to be, with different probabilities for different bubble types. If the type is a bonus bubble, another random number is used to decide which type of bonus it’ll be (again, with weighted probabilities). The new bubble is placed at a random x coordinate above the top of the screen and given random x and y velocities. Minimum and maximum speeds for horizontal and vertical movement depend on the current difficulty level, as do the relative probabilities of bubble types and the overall frequency of new bubble spawns.
Think about this setup for a moment. It has several downsides:
- The possibility of long stretches with few or no blue bubbles. These are the ones that turn your greens into points. When you have a large stack of green bubbles attached, you need to touch a blue bubble to get rid of them.
If lots of blue bubbles are floating around, the game becomes way too easy. But as the blue bubbles become sparser, the probability of an unlucky streak with no blue bubbles being around at all increases. Also, not every blue bubble is reachable from anywhere on the screen (especially not if you have greens attached). If blue bubbles are sparse, there’s also the possibility of a streak where all blue bubbles are unreachable.
- The possibility of too many bonus bubbles occuring in a short timespan. This is the opposite of 1. While bonus bubbles are generally rare, their occurence is entirely up to the RNG. Given enough game rounds, it’s possible to pick up four or five extra lifes in a single round.
- The way bubbles are placed and set in motion has a drawback as well: Recall that new bubbles are placed above the top of the screen and given random x and y velocities, within defined ranges. This results in uneven bubble densities across the playing field. The probability of encountering a bubble is slightly higher near the top of the screen than at the bottom. The density gradient depends on the maximum possible x speed, relative to the y speed.
This figure shows what two of these gradients look like, with varying maximum speeds. These gradients were created with a simple Processing script, which you can check out here.
It turns out that with large enough horizontal margins to the sides of the screen, in which bubbles can be spawned, this issue doesn’t really affect Drifts (although it might affect your game!).
The first two problems could be solved by taking a different approach to bubble spawning: instead of spawning bubbles at a specified frequency and then determining their type by a throw of the dice, we could have had separate spawning counters for each bubble type, with minimum and maximum frequencies based on the current difficulty level (e.g. Spawn a green bubble every 0.4-0.5 seconds, a purple one every 0.25-0.3, etc.).
This would solve both the problem of streaks of too few blue bubbles and streaks of too many bonuses. Instead of reducing the probability of a new bubble being a bonus bubble, we could have simply randomized the intervals at which bonus bubbles are spawned, with large enough minimum intervals.
Note that this doesn’t take into account that new bubbles are spawned above the top of the screen with random velocities, so it could still happen that a much needed blue bubble would be onscreen for far too little time, given an unlucky position/velocity combination. This is something that could be fixed by tweaking the initialization of these bubbles – for instance, having bubbles always go through a center area of the screen would take care of this problem. Tweaks like this should be applied with caution, because they can change the bubble density gradient, but for an infrequent bubble type like the blue bubble, my guess is that would not be a problem.
Another thing that calls for special attention is how much more powerful some bonus items are than others. Even if the intervals in which bonus bubbles appear were fixed at a specified range, a streak of 1UPs is still more powerful than a streak of score boosts. It would have made sense to not only make other bonus bubbles more probable than extra lifes (which we did), but to also limit the total number of extra lifes a player can get per game round (instead we simply turn them off after level 10 or so).
These fixes would have required us to completely redo the difficulty progression in Drifts (accounting for the shift from a fixed frequency / random type approach to ranged frequencies for each type), so it pays off to think about how random processes in your game can result in unwanted artefacts early in the development process, rather than discovering them in beta testing.
If you’re developing a two player Tetris, it’s probably a good idea to think about adding mechanisms that make sure one player doesn’t get an extremely unlucky streak of oddly shaped blocks, before fine tuning the rest of the gameplay parameters.
The importance of such mechanisms is proportional to the amount of influence that luck will have on a skilled player’s performance. It may not be necessary that every Tetris round features an equal number of I blocks (if a skilled player can do without them), but it sure would have been nice if every round of Mario Kart Battle would have featured some red (or at least green) shells for each player.
“Bastet”, short for “Bastard Tetris”, is designed to let you play the most unlucky round of Tetris in the world by giving you only the pieces you have the least use for.
Shameless PlugCheck out my upcoming 80s-cartoon-themed space opera "Ace Ferrara And The Dino Menace"!