noobtuts

C++ Ask Oracle

Foreword

When making a video game chances are high that at some point we need to make a decision based on a certain probability. Our invention, the Ask Oracle function will take care of this problem.

The Problem

Let's assume we are implementing a Monster class. The monster should drop some coins with a 90% chance. As usual while developing a game we don't have much time to invest into algorithms like this, so we will just use the random function and a little comparing, then return something and that's it, right?

The game ships, and after a week or so we suddenly receive a lot of negative feedback from our players, complaining that the game is too hard. Suddenly we realize that the monster does not drop coins with a 90% chance at all, instead it's a 10% chance because our algorithm wasn't implemented correctly.

Let's prevent this from happening by implementing the Ask Oracle function.

Implementation

Note: 90% equals 0.90f as a floating point number. A 25% value would be 0.25f accordingly (and so on).

Since we want it to be as simple to use as possible, we want it to work like this:

#include <iostream>

void dropItems()
{
    // our event has a 90% chance (which means 0.90f)
    // so let's ask the "oracle" to see if it happens
    // or not    
    if (askOracle(0.90f)) {
       // drop coins or a sword or whatever
       std::cout<<"it happened!";
    }
}

The function is called "Ask Oracle" because of several adventure movies where people ask a magical oracle that predicts events that are happening. It's not the mathematically correct name for the function, but it's easy to remember and that's what matters for us.

The function itself is very short, yet can easily contain several mistakes if not implemented correctly (like using a a too high or too low chance than it should, like in monster example). Here it is:

bool askOracle(float percentChance)
{
    // Decides if a decision should
    // be done (true/false), when a
    // certain Percent Chance is given
    //
    // -> Don't forget to call randomize
    //    every once in a while!

    // generate random number between 0 and 99
    int randomNumber = rand() % 100;    
   
    // scale it so it's between 0 and 1
    float randomResult = (float)randomNumber * 0.01f;

    // compare to see if it "happened" or not
    return randomResult < percentChance;
}

The most important aspect here is the "rand() % 100" part. Let's go a bit more into detail about that.

The idea behind "rand() % 100"

Let's find out what rand() really does. If we read the documentation, we soon understand that rand() itself just gives us some kind of integer value, maybe small or maybe very huge.
But using the modulo (%) operator:

rand() % 100

gives us a value between 0 and 99. But wait, shouldn't it be between 0 and 100 because the chances should be between 0% and 100%?
Here comes the tricky part: while this might seem to make sense at first, we actually want the value to always be less than 100 (between 0 and 99). The reason is that if we call our function like this:

// 100% sure event
if (askOracle(1.00f)) {
   // do something
}

we want to be sure that askOracle returns true no matter what the rand() function returns, since the chance for the event to happen is 100% - which means nothing different than that it happens in any case. This is the reason why we generate a number between 0 and 99 - we make sure that if we call askOracle with a 100% chance, we always get a "true" as result (since the 100 is never reached, highest possible value is 99).

Note: it sounds a bit complicated, so feel free to play with the algorithm a bit to get convinced that this really works.

Summary

We just put a few lines of rather complicated code into a very easy to use function, which will save us a lot of stress in the future. Instead of bending our minds around the rand() function, we can simply call askOracle the next time.

One more thing to know about rand() is that we need to shuffle the random numbers again every once in a while. This process is usually called "Randomize", it makes sure that everything is as random as possible. One way to do this can be seen here:

srand(GetTickCount());

We should at least do this every time the game starts. If possible then even more often while the game is running.

Please keep in mind that this is just a very simple implementation of a slightly complex problem. There are a lot of ways to make this function more exact or faster, but since we are making video games and not satellite board computers, this implementation should satisfy our needs. Keeping it simple keeps the bugs always as far as possible.