Game Development Community

getting proportional random numbers possible?

by Isaac Barbosa · in Torque Game Builder · 03/03/2009 (9:02 am) · 7 replies

Hi,

I don´t know if this is possible, and even if this is the correct way to ask. I want to get a random number from a given quantity using getRandom(). Lets say I use

%pickANumber = getRandom(99);

to get a random number between 0 to 99, that works fine. But what if I want to have a 10% of chances to get a number from 0 to 9 instead of the whole 100 chance?

Purpose, I want to set a balanced letter proportion for a word game and I want to balance the number of letter the game grid has.

So I will state something like this in a function that I will call several times, up to 100

if(%pickANumber == "1" || %pickANumber == "2" || %pickANumber == "3")
%chosenLetter = "A";
else if(%pickANumber == "4")
{
%chosenLetter = "B";
}

I undestand than this way there is a 75% of chance to get an "A" and a 25% of chance to get a "B", but in the practice it will be that most of times I get only "A"

I hope this makes any sense

#1
03/03/2009 (12:13 pm)
Generate a number between 0 and 1, then use an interval comparison:

%num = getRandom();
if ( %num >= 0 && %num <= 0.1 )
{
    // 0-10%
}
else if ( %num > 0.1 && %num <= 0.2 )
{
    // 10%-20%
}
else if ( %num > 0.2 && %num <= 0.5 )
{
    // 20%-50%
}
else if ( %num > 0.5 && %num <= 1.0 )
{
    // 50%-100%
}
#2
03/04/2009 (8:19 am)
Hi Phillip,

That gave some problems in the practice so I keep with my actual code, I guess it is the same thing:

%color = getRandom(100);
   if(%color == 0 || %color == 1 || %color == 2 || %color == 3 || %color == 4 || %color == 5 || %color == 6 || %color == 7 || %color == 8 || %color == 9)
   {
      %color = "A";
      %thisletter = $letter[0]; 
      %isprotected = false;
      %colorGroup = %quedado;
   }
   if(%color == 10 || %color == 11 || %color == 12)
   {
      %color = "B";
      %thisletter = $letter[1]; 
      %isprotected = false;
      %colorGroup = %quedado;
   }
   if(%color == 13 || %color == 14 || %color == 15)
   {
      %color = "C";
      %thisletter = $letter[2]; 
      %isprotected = false;
      %colorGroup = %quedado;
   }
   if(%color == 16 || %color == 17 || %color == 18 || %color == 19 || %color == 20)
   { 
      %color = "D";
      %thisletter = $letter[3]; 
      %isprotected = false;
      %colorGroup = %quedado;
   }
   if(%color == 21 || %color == 22 || %color == 23 || %color == 24 || %color == 25 || %color == 26 || %color == 27 || %color == 28 || %color == 29)
   {
      %color = "E";
      %thisletter = $letter[4]; 
      %isprotected = false;
      %colorGroup = %quedado;
   }
   if(%color == 30 || %color == 31)
   {
      %color = "F";
      %thisletter = $letter[5]; 
      %isprotected = false;
      %colorGroup = %quedado;
   }
   if(%color == 32 || %color == 33 || %color == 34)
   {
      %color = "G";
      %thisletter = $letter[6]; 
      %isprotected = false;
      %colorGroup = %quedado;
   }
   if(%color == 35 || %color == 36)
   {
      %color = "H";
      %thisletter = $letter[7]; 
      %isprotected = false;
      %colorGroup = %quedado;
   }
and so on... until get 100

Thanks
#3
03/04/2009 (2:13 pm)
That's a whole lot of 'if' statements. If you don't mind my saying, you may want to make it an incrementing case statement or incrementing if statements. I took the liberty of looking at the distribution of letters from a site and they have some rounding problems, but you get the point.

LETTER	Unique%	Cumulative %
E	12.702	12.702
T	9.056	21.758
A	8.167	29.925
O	7.507	37.432
I	6.996	44.428
N	6.749	51.177
S	6.327	57.504
H	6.094	63.598
R	5.987	69.585
D	4.253	73.838
L	4.025	77.863
C	2.782	80.645
U	2.758	83.403
M	2.406	85.809
W	2.36	88.169
F	2.228	90.397
G	2.015	92.412
Y	1.974	94.386
P	1.929	96.315
B	1.492	97.807
V	0.978	98.785
K	0.772	99.557
J	0.153	99.71
X	0.15	99.86
Q	0.095	99.955
Z	0.074	100.029

The point of this is that if you're going to give a letter, the most common ones should come first in your check, then return the value as soon as you find it. Also, it might just be good to have a FIFO buffer of upcoming letters so that you don't get hit with a request for 7 letters suddenly, not that it should be a problem with that small number.

Source for individual number probabilities:
H. Beker and F. Piper, Cipher Systems, Wiley-Interscience, 1982.
Source for the cumulative:
Jason Ravencroft and Microsoft Office Excel 2007. =D
#4
03/04/2009 (3:05 pm)
Here's another approach to getting weighted random values,
which requires a little bit more programming up-front,
but is quite flexible once you've done that.

Basically you declare each value you'd like to have and give it a "weight".
If you declare two items with the same weight, they have the same probability of being returned. You can picture it as you're putting a bunch of marbles of different colors in a bag, and the weight is the number of marbles of that color you put in. Except the bag is re-filled every time you ask for a marble.

So for example to set up say the scrabble tile probabilities,
you would set K, J, X, Q, and Z all to weight 1,
then B, C, M, P, F, H, V, W, Y, <blank> all to weight 2,
then G to 3, and so on.

here's the code.
function ensureRandomItemManager()
{
   if (!isObject(gRandomItemManager))
   {
      new ScriptObject(gRandomItemManager);
      if (isObject(MissionCleanup))
      {
         MissionCleanup.add(gRandomItemManager);
         gRandomItemManager.numItems = 0;
      }
   }
}

function clearRandomItems()
{
   ensureRandomItemManager();
   gRandomItemManager.numItems = 0;
}

function declareRandomItem(%itemName, %itemWeight)
{
   ensureRandomItemManager();
   
   %n = gRandomItemManager.numItems;
   gRandomItemManager.itemName  [%n]          = %itemName;
   gRandomItemManager.itemWeight[%n]          = %itemWeight;
   gRandomItemManager.weightsNeedNormalizing  = true;
   gRandomItemManager.numItems               += 1;
   
}

function getRandomItem()
{
   ensureRandomItemManager();
   
   if (gRandomItemManager.weightsNeedNormalizing)
   {
      gRandomItemManager.weightsNeedNormalizing = false;
      
      %totalWeight = 0;
      for (%n = 0; %n < gRandomItemManager.numItems; %n++)
      {
         gRandomItemManager.itemWeightCumulative[%n]  = gRandomItemManager.itemWeight[%n] + %totalWeight;
         %totalWeight                                += gRandomItemManager.itemWeight[%n];
//       echo(gRandomItemManager.itemName[%n] SPC gRandomItemManager.itemWeight[%n] SPC gRandomItemManager.itemWeightCumulative[%n]);
      }
      
      gRandomItemManager.totalWeight = %totalWeight;
   }
   
   %rand = getRandom(0, gRandomItemManager.totalWeight - 1);
   
   for (%n = 0; %n < gRandomItemManager.numItems; %n++)
   {
      if (%rand < gRandomItemManager.itemWeightCumulative[%n])
      {
         return gRandomItemManager.itemName[%n];
      }
   }
   
   error("something went wrong.");
   return "";
}

function testRandomItems(%iterations)
{
   %totals["A"] = 0;
   %totals["B"] = 0;
   %totals["C"] = 0;
   %totals["D"] = 0;
   
   clearRandomItems();
   declareRandomItem("A", 1);
   declareRandomItem("B", 1);
   declareRandomItem("C", 1);
   declareRandomItem("D", 3);

   for (%n = 0; %n < %iterations; %n++)
   {
      %item = getRandomItem();
      %totals[%item] += 1;
//    echo(%item);
   }
   
   echo("A -" SPC %totals["A"]);
   echo("B -" SPC %totals["B"]);
   echo("C -" SPC %totals["C"]);
   echo("D -" SPC %totals["D"]);
}

and here's an example run where A, B, and C have weight 1, and D has weight 3:
==>testRandomItems(6000);
A - 996
B - 1021
C - 992
D - 2991

#5
03/04/2009 (3:46 pm)
Jason,

For the purpose I´m pursuing I will try something with this.

Orion,

That code seems pretty interesting, I should study it to see what can be useful.

Thanks guys!
#6
03/04/2009 (4:03 pm)
I'll chime in for fun. Why not just make a letter string and randomly pull from it? Something like:

%letters = "AAAAAABBBCCCCCCDDEEEEEEEFFFFGGHHHHH"; // etc...
%choice = %letters[getRandom(0,strlen(%letters) - 1)];

That should be a lot faster to tune and use.
#7
04/13/2009 (8:34 am)
I forgot to say Thanks.

Thanks Chris,
this did the trick. I pull a random letter from the string and then I update the string for a new pick up if needed.