Posted on by

Most of us have dealt with generating random numbers at some point in our app development. In this tutorial, we’ll look at the core syntax for generating random numbers. Later, we’ll walk through a “dice rolling” mechanism for more advanced use cases.

Seeding the Generator

One of the most common mistakes in requesting random numbers is failing to seed the random number generator. Outside of some very high-tech, math-intensive, complex methods, most computers/devices generate pseudo-random numbers. As such, we must seed the random number generator with a value to start the generation. If we seed the generator with the same value, we’ll get the same pattern of random numbers. This can be useful in some cases, but in most cases we’ll want different numbers. To achieve this, we can add this line to our main.lua file:

math.randomseed( os.time() )

Since os.time() returns a different value each time we run the app, this call will seed the random number generator with a fairly random starting value. Conveniently, we only need to do this once, typically near the top of our main code.

With that out of the way, let’s look at the math.random() function which has three “modes”:

  • value = math.random() — returns a floating point number between 0 and 1.
  • value = math.random( maxVal ) — returns an integer between 1 and maxVal.
  • value = math.random( startVal, maxVal ) — returns an integer between startVal and maxVal.

The first mode is the way most programming languages work, but since most people don’t really need floating point numbers between 0 and 1, we’ve traditionally needed to multiply this number by the maximum value. For instance, if we want a number between 1 and 10, the C code would look like this:

int number = int( rand() * 10 ) + 1

Fortunately, Lua provides a convenience method to help with this, in the form of the 2nd structure above. Simply pass in the maximum value to get a number between 1 and maxVal.

local number = math.random( 10 )

In the instances where the range should not start at 1, Lua offers the 3rd structure. For instance, if we want to spawn an enemy in the middle third of the screen, this code would be useful to generate its x position:

local number = math.random( 100, 220 )

Roll the Dice!

The above functions are useful, but what if we need something more complex? In many games, for instance Dungeons & Dragons™ and similar role-playing games, players need to roll three 6-sided dice to determine their character’s strength. In this example, character strength values range in the 3-18 range, but only a few players would roll for a strength of 3 or 18. Most characters would end up in the range of 10 to 11. Many board games like Monopoly™ use two 6-sided dice, so rolling a summed total of 2 or 12 is rare, while sums between 6 and 8 are more common. In statistics, this is referred to as a bell curve. However, the standard random number generator generates a flat line of numbers where each has an equal probability to be selected. As a result, dice rolling needs can get quite complex. Additionally, some games feature many varieties of dice in addition to the traditional 6-sided version — there are dice with 4 sides, 8 sides, 10, 12, 20, and even 30!

Sometimes we’ll also need to add or subtract “modifiers” to the results. These gaming systems use a fairly common “language” for specifying the dice and modifiers. For instance, consider:

  • 3d6 — Generate a number by rolling three 6-sided dice, then add them up.
  • 2d8+3 — Generate a number by rolling two 8-sided dice, add them up, and then add 3 to the total.

Rolling physical dice may be fun, but how can this be accomplished in Corona SDK? First, we should establish the “language” of the dice. In this tutorial, we’ll use a variant of the Myth-Weavers system. In this system, we start with the number of dice, then we write the letter d, followed by the number of sides to the dice. This is followed by an optional + or  and a number that gets added or subtracted. To make it a bit more functional, this system also supports the idea of keeping some dice results while discarding others. In the role-playing example, since most heroes need better stats than normal humans, their character stats are rolled using four 6-sided dice, but only the highest 3 dice results are kept and added together. This dice string would be represented as 4d6^3. Now let’s inspect the Lua code:

local function rollDice( dicePattern )

   -- Dice pattern 3d6+3k3
   -- First number : number of dice
   -- d : required string
   -- Second number : sides to the dice
   -- +/- : optional modifier
   -- ^/k : optional string; '^' keeps the high values, 'k' keeps the low values
   -- Third number : number of dice to keep, i.e. 4d6^3 keeps the best three numbers

   local dice = {}
   local random = math.random
   local total = 0

   -- Parse the string
   local count, sides, sign, modifier, keepHiLo, keep = string.match( dicePattern, "(%d+)[dD](%d+)([%+%-]*)(%d*)([%^k]*)(%d*)" )

   modifier = tonumber(modifier)
   keep = tonumber(keep)
   if ( modifier == nil ) then
      modifier = 0
   end
   if ( sign == "-" and modifier > 0 ) then
      modifier = modifier * -1
   end

   for i = 1, count do
      dice[i] = random( sides )
   end

   if ( keep ) then
      local function keepCompare( a, b )
         return a > b
      end
      if ( keepHiLo == "k" ) then
         table.sort( dice )
      else
         table.sort( dice, keepCompare )
      end

      for i = 1, keep do
         total = total + dice[i]
      end
   else
      for i = 1, count do
         total = total + dice[i]
      end
   end
   total = total + modifier
   return total, dice
end

Deconstructing the Code

Let’s step through the code to see how it works:

   local dice = {}
   local random = math.random
   local total = 0

Here, we create a table to hold the individual dice rolls. We also localize the math.random() function for optimal performance and set the total value to 0.

Next we need to parse the dicePattern argument using string.match():

   local count, sides, modifier, keepHiLo, keep = string.match( dicePattern, "(%d+)[dD](%d+)([%+%-]*)(%d*)([%^k]*)(%d*)" )

   modifier = tonumber(modifier)
   keep = tonumber(keep)
   if ( modifier == nil ) then
      modifier = 0
   end
   if ( sign == "-" and modifier > 0 ) then
      modifier = modifier * -1
   end

In Lua, string.match() requires two parameters: the string we want to search through and a pattern of what to search for. All of the matches are returned to the variables on the left side of the = sign. In this example, we search for a number ((%d+)) followed by the letter d ([dD]). Note that we support both a lowercase d and an uppercase D as a minor fail-safe. Next, we look for the sides to the dice ((%d+)). In Lua pattern matching, %d indicates a single number while %d+ indicates any number with 1 or more digits.

Next, we must search for the optional parts of the string. The next pattern set (([%+%-]*)) looks for a + or  sign and stores the results in a variable named sign. These are special symbols, so we need to escape them with a leading % sign. By placing them within square brackets, Lua knows that either one is valid. Finally, the * at the end indicates that zero or more matches are allowed, thus making it optional. The next pattern set ((%d*)) captures an optional number for the value of the modifier. Following this, we search for either a carrot ^ or the letter k to determine the number of dice to keep out of the set. The carrot is another special symbol, so it needs to be escaped with a percent sign. Finally, we capture the count of dice to keep. In this match, we’ll actually get an empty string returned for modifier and keep. By converting that string to a number with tonumber(), it will either return a number or nil. If the modifier is nil, make it 0. Then check the sign variable and, if it’s a minus sign, multiply modifier by -1.

Now let’s generate the rolls:

   for i = 1, count do
      dice[i] = random( sides )
   end

   if ( keep ) then
      local function keepCompare( a, b )
         return a > b
      end
      if ( keepHiLo == "k" ) then
         table.sort( dice )
      else
         table.sort( dice, keepCompare )
      end

      for i = 1, keep do
         total = total + dice[i]
      end
   else
      for i = 1, count do
         total = total + dice[i]
      end
   end
   total = total + modifier
   return total, dice

The first for loop loads the dice array with a number randomly generated from the number of specified sides to the dice. If we ask for 4d6, we’ll get an array of 4 numbers, each number ranging 1-6. Then, if keep is specified, we’ll get the sum of the values, up to the keep amount. To figure out which dice to keep, the array must be sorted using the table.sort()function. By checking the value of the keepHiLo value parsed from the string, we can sort the table from either low to high or vice-versa. Finally, we loop through the array of sorted numbers and only count those that we want to keep. Finally, to wrap up the function, we add the modifier, return the total of the rolls, and also return the array of rolls in case we want the calling routine to display the values.

Making a Roll

With the function established, we can call the function using a valid dice string and then access the dice values after the call:

local result, rolls = rollDice( "3d6" )
for i = 1, #rolls do
   print( i, rolls[i] )
end
print( result )
result, rolls = rollDice( "4d6+1^3" )
for i = 1, #rolls do
   print( i, rolls[i] )
end
print( result )

In Conclusion

Hopefully you have a clearer understanding of random number generation along with a function that you can use as an advanced dice rolling mechanism. Armed with this knowledge, you can implement ranges of random numbers that are not evenly distributed, allowing your app to easily support common, uncommon, and even rare events.


Posted by . Thanks for reading...

One Response to “Tutorial: Advanced Random Numbers”

Leave a Reply

  • (Will Not Be Published)