For an online game (MMORPG) I want to create characters (players) with random strength values. The stronger the characters are, the less should exist of this sort.
E
something like this
<?php
$rand = rand(1,10);
switch ($rand) {
case 1:
echo "band 1";
break;
case 2:
case 3:
echo "band 2";
break;
case 4:
case 5:
case 6:
echo "band 3";
break;
default:
echo "band 4";
break;
}
?>
Band 1 being the strongest, band 4 being the weakest.
Ofcourse this is basic, you would want to refactor this to use loops instead of hardcoded switches, but you get the idea :)
The simplest way to do this would be to provide 'bands' for where a random number should go. In your example, you have 15 players so you could have:
rand < 1/15, highest strength
1/15 < rand < 3/15, second highest
3/15 < rand < 6/15, third highest
6/15 < rand < 10/15, fourth highest
10/15 < rand < 15/15, lowest strength
You could also parameterise such a function with a 'max' number of each band that you allow and when the band is filled, it is subsumed into the next lowest existing band (apart from the bottom band, which would be subsumed into the next highest) to ensure only a certain number of each with a random distribution.
Edit adding from my comments:
To get a floating range pyramid structure the best function would most likely be a logarithm. The formula:
11 - log10(rand)
would work (with log10 being a logarithm with base 10) as this would give ranges like:
1 < rand < 10 = 9 < strength < 10
10 < rand < 100 = 8 < strength < 9
100 < rand < 1000 = 7 < strength < 8
etc.
but rand would need to range from 1 to 10^10 which would require a lot of randomness (more than most random generators can manage). To get a random number in this sort of range you could multiply some together. 3 random numbers could manage it:
11 - log10(rand1 * rand2 * rand3)
with rand1 having range 1-10000 and rand2 and rand3 having range 1-1000. This would skew the distribution away from a proper pyramid slightly though (more likely to have numbers in the centre I believe) so it may not be suitable.
workmad3 has the start of it down, I think, but there's a catch - you need to track your bucket sizes and whether or not they're full. A random number generator won't guarantee that. You'll need to assign your bucket values (strenghs) and sizes (number of people), and let your random generator tell you which bucket to drop the player into - if that one is full, 'overflow' to the next lower.
As to assigning the bucket sizes for a given strength value, that's the tricky bit (and I think what you're really working at). The characteristics of your desired distribution are critical. If you want a linear drop (which the pyramid shape hints at), a line of the form
strength = max_strength - m(number_characters)
would work. Varying the value of m would change the speed at which the line drops off, and will basically limit your max number of total characters. If you're looking for a more sophisticated way for the strength values to drop off, you could use a parabolic or hyperbolic curve - these are a bit more complex, but give you very different characteristics.
It's probably easiest to use percentages in this case.
From your examples would approximately be (converted to an array for ease of use later):
$strength[1] = .3; // start with a key of 1
$strength[2] = .26;
$strength[3] = .21;
$strength[4] = .15;
$strength[5] = .08;
That way, you can generate a random number using mt_rand()
and divide by the maximum possible value to get a number between 0 and 1:
$rand = mt_rand() / mt_getrandmax(); // rand is some random value between 0 and 1
Then you can use a foreach
statement to isolate each case:
$comparisonPercentage = 1;
$selectedLevel = count($strength); // covers the case where mt_rand() returns 0
foreach($strength as $level => $currentPercentage)
{
$comparisonPercentage -= $currentPercentage;
if ($rand > $comparisonPercentage)
{
$selectedLevel = $level;
break;
}
}
// $selectedLevel contains the level you need...
If you do it this way, you only have to change the $strength
array if you need to fiddle with the percentages.
generate a random number between 0 and 40000, if its between 0 and 12000, assign strength 1, between 12000 and 22500 assign 2 etc.
Edit: for progressive values between 0 and 10 use the square root of a random number between 0 and 100, then substract if from 10
For results between 1.1 and 9.9 the formula would be in pseudocode)
strength = 10 - sqrt(rand(1..79))
You can simulate a distribution such as the one you described using a logarithmic function. The following will return a random strength value between 1.1 and 9.9:
function getRandomStrength()
{
$rand = mt_rand() / mt_getrandmax();
return round(pow(M_E, ($rand - 1.033) / -0.45), 1);
}
Distribution over 1000 runs (where S
is the strength value floored):
S | Count
--+------
1 - 290
2 - 174
3 - 141
4 - 101
5 - 84
6 - 67
7 - 55
8 - 50
9 - 38
This answer was updated to include a strength value of 1.1 (which wasn't included before because of the rounding) and to fix the name of the mt_getrandmax() function