3
\$\begingroup\$

I appear to be awful at describing the question so I'll try and describe the problem.

I want to add a random amount of heads to my creatures but I want to be able to determine several things. a) The minimum number of possible heads b) The maximum number of possible heads c) The probability of the number being high/low within the above values.

So i could add heads like so: addHeads(5, 10, 0.5); // should produce creatures with "around" 7.5 heads but they could have anywhere from 5 to 10.

So random number generation isn't the problem, but controlling and actually using them in a game is. :D

\$\endgroup\$
2
  • 1
    \$\begingroup\$ you'll need to precisely define how the third parameter will affect the distribution, then when you have that (parameterized) function take the integral and invert it \$\endgroup\$ Commented Apr 27, 2013 at 0:59
  • \$\begingroup\$ This question: gamedev.stackexchange.com/questions/12638/… \$\endgroup\$
    – Nevermind
    Commented Apr 27, 2013 at 5:04

3 Answers 3

7
\$\begingroup\$

One way to do it is to apply a power function: $$f(x) = ax^p+b$$

Start with a random number \$x\$ in [0, 1] and then raise it to the power of some positive number \$p\$. Powers < 1 will bias upward, i.e. the numbers will be more likely to be higher than lower within [0, 1], and powers > 1 will bias downward. Then use multiplication (\$a\$) and addition (\$b\$) to shift the range of numbers from [0, 1] to your desired range. In pseudocode:

function random(low, high, bias)
{
    float r = rand01();    // random between 0 and 1
    r = pow(r, bias);
    return low + (high - low) * r;
}

// Examples:
random(5, 10, 1.0);  // between 5 and 10, average is 7.5
random(5, 10, 0.5);  // between 5 and 10, average is somewhere around 8.5
random(5, 10, 2.0);  // between 5 and 10, average is somewhere around 6.5

Here's a plot of the second example:

enter image description here

On the horizontal is the initial random number in [0, 1] and on the vertical is the output. You can see that something like 75% of the initial range is mapped to values higher than 7.5, and 25% of the initial range is mapped below 7.5. So the result is that numbers generated by this function are more likely to be higher.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ The only suggestion I'd say to this method is that, for whole, discrete results, especially in low numbers, the common sense mins and maxes result in a skew away from the min and max. Wanting, to use the example provided in the question, a small number for "number of heads", say 1-4, will result in greater frequency of 2 or 3 with traditional rounding. In this case, the max should be one higher with the result rounded down, lest an unintuitive 1:2:2:1 ratio form. \$\endgroup\$
    – Attackfarm
    Commented Apr 27, 2013 at 5:23
  • \$\begingroup\$ This answer makes the most sense to me. To clarify, to implement Attackfarm's fix, I want to increase the max by one before the the calculation and round the whole result down at the end? \$\endgroup\$
    – Caustic
    Commented Apr 27, 2013 at 6:08
  • \$\begingroup\$ Yes. There are other ways to do it, but that's the simplest. Though, I must say, Byte56's answer is definitely much more powerful (though slightly harder to implement, since this is just a single Math-library function), this would suit simpler needs fine. \$\endgroup\$
    – Attackfarm
    Commented Apr 27, 2013 at 6:42
4
\$\begingroup\$

The usual answer will be some code that can tweak the random number distribution to give you the properties you want. I'd suggest instead working backwards:

  1. first decide what distribution you actually want and draw it out
  2. calculate the “cumulative distribution” which is the sum of the function in step 1
  3. choose a random number from the cumulative distribution
  4. look up the number from the original distribution you drew

This way you don't ever actually need to find a function (like power or logarithm) that matches your desired behavior. You can directly model your desired behavior.

How do you implement this? You can approximate step 1 with a table. For step 2 you add the values in the table. For step 3 you pick a random number from 0 to the sum in the cumulative table. Then for step 4 you look for the corresponding entry in the cumulative table.

I don't yet have a great description of this technique, but I've written a little bit in the “Designing your own distribution” section of this page.

This approach probably won't work for everything but I mention it because tables are nice for designers to edit, and the designer is not limited to existing mathematical functions but instead can choose any arbitrary wacky shape. For example, if you decide that a prime number of heads should be unusually rare, that's easy to write in a table, but it might be harder to find a mathematical function that fits that shape.

\$\endgroup\$
2
  • \$\begingroup\$ This probably makes sense but not to me, I'm afraid. I can assume what a distribution is in this context but draw it out? What does this mean? I'm guessing you don't mean on paper.. \$\endgroup\$
    – Caustic
    Commented Apr 27, 2013 at 6:06
  • \$\begingroup\$ I do mean on paper :) … first you want to decide on what shape you want, because it's part of your game design. Only after you have a shape do you figure out the corresponding formulas or code. You're not limited to using a standard function like sqrt or pow. Don't limit yourself! :) You can use any arbitrary shape encoded in a table (“piecewise linear function”). \$\endgroup\$
    – amitp
    Commented May 14, 2013 at 17:33
1
\$\begingroup\$

I have a harvest system implemented in my game that utilizes rejection sampling. Each item has a weighted chance of being produced. It's easy to assign weights to certain items and get one randomly using that weighted chance. This is similar to your situation, except you don't have harvest items, you have number of heads. So, you'd first assign a weighted chance to each head count value for the values you want.

So we have # of heads : weighted chance

5 heads:2
6 heads:4
7 heads:3
8 heads:1
9 heads:2
10 heads:1

The weighted chance values don't have to add up to anything specific, but you should know that they are going to be relative to each other.

Then we go through and make a list of them all:

ArrayList<ProducedItemInfo> tmpList = new ArrayList<ProducedItemInfo>();
    int totalChance = 0;
    for (HeadWeightPair hwp: headWeightPairs) {
        if(conditionsMetFor(hwp)) {
            totalChance += hwp.weightedChance;
            tmpList.add(hwp);
        }
    } //add up the total weight and create a list of the available options
      // at this stage, you could have conditions that would preclude certain options
      // for example, you could check the size of the creature and
      // not allow high numbers of heads
      // if you'll never have conditions, just generate this list once and keep it
    //now we go through removing a random value from the totalChance
    // if one of the items is less than the total, we return that item
    if (totalChance > 0) {
        int rand = randomInt(totalChance);
        for (HeadWeightPair hwp: tmpList) {
            if (rand < hwp.weightedChance) { return hwp.headCount; }
            rand -= hwp.weightedChance;
        }
    }

It's log(n) if you have a pre-compiled list (if you don't have conditions you can do that).

\$\endgroup\$
1
  • \$\begingroup\$ This seems like a great way of doing it when you want to specify the chance individually and I may use it for another part of my project, so thank you. \$\endgroup\$
    – Caustic
    Commented Apr 27, 2013 at 6:15

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .