Need a way to choose random outcomes that is flexible, scalable, and adaptive? This might be for you. In writing a power up manager script for a game, I stumbled on this problem and after a long time searching, finding nothing, I came up with the following solution.
This solution is easy to add more possible outcomes to at a later date, making it highly future proof; it will turn probabilities into solid numbers without code needing to be re-written, making it extremely manageable to work with; and will adjust probabilities itself if outcomes are disabled or enabled.
Let us say that we have three possible outcomes with different chances of happening:
• Outcome 1: 20%
• Outcome 2: 50%
• Outcome 3: 30%
In my examples I will be using C# but I will go through the theory so this can really be applied to anything.
A typical way to implement this would be to order the outcomes by probability, generate a number between 0 and 1, and use if statements:
This is compact, but it doesn’t allow for any flexibility as numbers have to be re-totalled and changed. There is also no simple way to remove a possible outcome from the pool and when I first started working with the problem I considered an if statement for each outcome to see if it was active but that would mean looping around to try again if it failed. I wanted something better.
Here is my code. Have a quick skim and we’ll get into it below.
Alright, so first things first we add some booleans to our class for each outcome to keep track of which outcomes are active or inactive. You may wish to keep track of this elsewhere, but I think attaching them to the class makes them easy to access, especially for the Unity3D inspector which can set their values and allow me to enable or disable outcomes on a level by level basis.
Next we set up a list that we will fill with our active outcomes and create the random generator for use later on. After that we need a global variable that will keep track of the total chance of the outcomes, but I will talk more about that shortly.
One more thing we need before we start is an Outcome class to populate the list with. This will contain three things: the name of the outcome (not strictly required but useful for printing outcomes, logging errors, etc.), the chance of the outcome occurring (I will work in percentages though it can be out of anything), and a key that connects it to the switch that will trigger the outcome.
Now everything is in place we can create our list and fill it with only the currently active outcomes. This list follows a clear format and can be added to very easily in the future.
This is the heart of this solution. Rather than order these by outcome chance, then add them together to get probabilities and use if statements to check if our randomly generated number is higher than each outcome’s, we will think about this visually. Below shows each outcome as a block between 0% and 100%.
Regardless of order, Outcome 2 still covers 50% of the total probability.
This can be in any order as no matter where outcome 2 is, it makes up 50% of the total and half of all random numbers from 1 to 100 will fall within outcome 2’s block. This means new outcomes can be added quickly to anywhere in the list!
And what if an outcome is disabled? Let’s say that outcome 2 is disabled and our list contains only outcomes 1 & 3.
With Outcome 2 disabled the code still works.
In the list outcome 1 takes up 20/50 (or 40%) and outcome 3 takes up 30/50 (or 60%). This allows the programme to continue functioning without any of the probabilities needing to be changed.
To implement this we can loop through the list and store the total in our totalChance variable. We can also check that probabilities add up to 100% if that is necessary.
Now we have everything we need to run the random number generator. I have chosen to generate an integer between 1 and the total of the chances (+1 as the upper bound of .Next() is exclusive). This means that the number will always land on an outcome in our active list even if the total changes.
In order to find which outcome we have landed on, we will move through the list subtracting the outcome chances as we go. This is a very effective way of moving through the list as it does not require keeping track of all of the outcome chances, just subtracting them and moving on. Let’s look at the code and then a few examples.
Generate a number and subtract probabilities until zero or lower.
We reach zero when subtracting Outcome 3 so that is our answer.
The process is the same if an outcome is disabled.
And now we pass the SwitchKey value of the outcome to the switch and execute the outcome!
And there we have it, a flexible, scalable, and adaptive random outcome generator that is simple and easy to work with. For maximum flexibility, parts of this could be moved to methods, such as the number generation and the switch allowing them to be called when needed. Another option is to move the list to a new method and start it by clearing the list before adding to it, allowing the list to be refreshed while the code is running.
Here is the complete code for this example. I will be using this random outcome generator in my game and though I will be making some changes to turn it into a power up manager, this is the blueprint that I will be sticking to: