Is this algorithm (probabilities calculations) going to perform bad for my game?

Started by
5 comments, last by Alberth 3 years, 4 months ago

I've made an algorithm that takes as input a list of events and returns the only one that occurred (The algorithm makes sure that only one event will occur and the one with the highest probability should be more likely to occur).

The way that it works is by looping through the events, checking each event if it occurred and if not it removes it from the list until there is only one event left. Because the events with probabilities closer to 0 are more likely to be removed, then it is more likely that the one that is left is actually the event with the highest probability.

Because there is a chance that the loop will never terminate, or take a lot of iterations until it does, I stop the loop if a number of tries have been reached. This is actually a really rare situation but I must make sure the algorithm won't starve my game framerate.

Also if anyone knows a better way to achieve this, I'm listening!

I've done some tests and it seems to be working pretty great!
Results (Right-click the image below and open it in a new tab to see it better):

using System.Collections.Generic;
using UnityEngine;

public class Probability
{


    private static readonly System.Random m_Rand = new System.Random();

    


    /*
     * @param probability The probability to check for a chance.
     * @return True or False. The closer to 1 the higher the chance to return true.
     */
    public static bool Chance(float probability) 
    {

        //Make sure the probability is in the range [0,1].
        if (Debug.isDebugBuild)
            Debug.Assert(probability >= 0 && probability <= 1, "Probability must be a number in [0,1]");

        return (float)m_Rand.NextDouble() < probability;
    }





    /*
     * @param probabilities A List<KeyValuePair<string, float>> with the value representing the probability.
     * @return The key (string) of the probability that occured.
     */
    public static string OnlyOneWillOccur(List<KeyValuePair<string, float>> probabilities, int tries = 10) 
    {

        //Logging stuff.
        if (Debug.isDebugBuild) 
        {
            //Empty array assert.
            Debug.Assert(probabilities.Count > 0, "There is no point using this method with an empty list.");

            //Check the number of tries.
            Debug.Assert(tries >= 10 && tries <= 40, "Tries should be in the range of [10,40] for better performance.");

            //In debug build, check if the probabilities sum up to 1.
            float sum = 0;
            foreach (KeyValuePair<string, float> pair in probabilities)
                sum += pair.Value;
            Debug.Assert(sum == 1, "The sum of the probabilities must be equal to 1");
        }


        //Clone the original probabilities list.
        List<KeyValuePair<string, float>> probabs = new List<KeyValuePair<string, float>>(probabilities);

        //Number of tries. If foreach #1 does not get a Chance() that produces
        //false, then current_tries should be increamented!!!
        int current_tries = 0;

        //Loop until you reached all the tries or the probabilities array has only one item.
        while (current_tries < tries && probabs.Count > 1) 
        {
            //To be removed helper list.
            List<KeyValuePair<string, float>> toBeRemoved = new List<KeyValuePair<string, float>>();

            //Just a flag.
            bool shouldIncreaseTry = true;

            //foreach #1
            foreach (KeyValuePair<string, float> pair in probabs) 
            {
                if (Probability.Chance(pair.Value) == false)
                {
                    toBeRemoved.Add(pair);
                    shouldIncreaseTry = false;
                }
            }

            //Increase the number of tries.
            if (shouldIncreaseTry)
                current_tries++;


            //If all the items are going to be removed from the probabs list.
            //Reset the algorithm and increase the tries counter.
            if (toBeRemoved.Count == probabs.Count)
            {
                current_tries++;
                probabs = new List<KeyValuePair<string, float>>(probabilities);
                continue;
            }


            //Remove all the toBeRemoved key-value pairs from the probabilities.
            foreach (KeyValuePair<string, float> pair in toBeRemoved)
            {
                //Make sure the probabilities won't go empty!!!
                if (probabs.Count == 1)
                    break;

                //Remove the next item.
                probabs.Remove(pair);
            }
        }

        //Only one item in the list.
        if (probabs.Count == 1)
            return probabs[0].Key;

        //THE FOLLOWING SHOULD BE REALLY RARE TO HAPPEN.
        //More than one items in the list. Return one randomly.
        else if (probabs.Count > 1)
            return probabs[m_Rand.Next(0, probabs.Count)].Key;

        //This line should never be reached!!!
        if (Debug.isDebugBuild)
            Debug.Assert(false, "This line should never be reached. The code above should make sure that there is at least 1 item in the probabs array!");

        return null;
    }
}

void life()
{
  while (!succeed())
    try_again();

  die_happily();
}

 

Advertisement

For selecting an event in a set of events, one should generally:

  • Assign each event a weight; a probability
  • Sum the weights
  • Choose a random number between 0 and the total weight
  • The event that is chosen is the number that falls within that event's weighted range


For example:

weight  range   result
------  -----   ------
5       0-4     move directly to next target
8       5-12    randomly roam
15      13-27   roam toward player
100     28-128  nothing; continue current behavior; idle, if N/A

Each result then has a 4% (5/128), 6%, 12%, and 78% (respectively) of happening. Afterward, tweak the frequency in which an event is chosen or the probabilities as needed.

By the way, because I think I'm trying to reinvent the wheel, is there a C# library that can do that kind of math? Probabilities and statistics? I mean I'm trying to make a game not a math library lol :P


void life()
{
  while (!succeed())
    try_again();

  die_happily();
}

 

@babaliaris You haven't really explained your game so it's impossible to say if it will work for your game or not. However, with the statement “I've done some tests and it seems to be working pretty great!” I'm not sure what there is left to say… Seems you have it nailed down. I wouldn't worry about micro optimizations until you actually see a problem. Just move on for now since it's already working great.

On a side note… perhaps you could look into fuzzy logic?

AtomicWinter said:

@babaliaris You haven't really explained your game so it's impossible to say if it will work for your game or not. However, with the statement “I've done some tests and it seems to be working pretty great!” I'm not sure what there is left to say… Seems you have it nailed down. I wouldn't worry about micro optimizations until you actually see a problem. Just move on for now since it's already working great.

On a side note… perhaps you could look into fuzzy logic?

That's what I'm going to do, thanks!


void life()
{
  while (!succeed())
    try_again();

  die_happily();
}

 

The proposal by fastcall22 is the usual solution where you get an answer with one draw, and one walk over the original list until you find the item that you should get. It's a few lines of code:

float sum = 0;
foreach (KeyValuePair<string, float> pair in probabilities) sum += pair.Value;

float val = sum * random(); // 'random' should return a real value in [0, 1)
foreach (KeyValuePair<string, float> pair in probabilities) {
    val -= pair.value;
    if (val <= 0) return pair;
}
// Should never be reached but you may want to return the last entry of the list to be on the safe side.

This line is very wrong!

Debug.Assert(sum == 1, "The sum of the probabilities must be equal to 1");

Computations in math (on paper) has infinite precision, which is why you learned the sum of probabilities must be exactly 1. However, computations in a computer have finite precision, so you generally get rounding errors after computing floating point. Eg

> 10 * 0.14 == 1.4
False

See? So while in math the equation would hold, in a computer it doesn't necessarily hold as well. Above, it's apparently off by 2.220446049250313e-16 it tells me. A very small number, but not 0 (what “==” requires).

For more information, read about “what every programmer should know about floating point” on the web.

This topic is closed to new replies.

Advertisement