Tutorial: Advanced Random Numbers

Tutorial: Advanced Random Numbers

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:

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:

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.

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:

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:

Deconstructing the Code

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

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():

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:

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:

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.

Tags:
,
Rob Miracle
[email protected]

Rob is the Developer Relations Manager for Corona Labs. Besides being passionate about helping other developers make great games using Corona, he is also enjoys making games in his spare time. Rob has been coding games since 1979 from personal computers to mainframes. He has over 16 years professional experience in the gaming industry.

5 Comments
  • Nathan Gotch
    Posted at 10:23h, 13 March

    It is really such a great and easy to understand tutorial, thanks a lot for sharing this.

  • Krystian Szczęsny
    Posted at 03:29h, 06 September

    Thanks for the tutorial, nice one.
    However everyone should keep in mind, that lua random number generation is… an utter crap.
    I know that even when calling random function, if it returns ‘1’ 5 times in a row it is still considered random, but that means random generator does not behave properly.
    A simple example from today: I need to generate 12 random numbers between 1 and 400. Most of the time I end up with at least 3 equal numbers in my list. Yes, I do use randomseed. Else I would end up with the same set of numbers every time.
    Once we forgot to put the randomseed in our game… how cool it was to see that everytime players started the game they had exactly the same layout created.

  • kk
    Posted at 06:50h, 26 May

    I am a newcomer, would you show a example when draw out some random numbers from one group (first round), and then put into another group (second round) for further random again.

    • Rob Miracle
      Posted at 07:32h, 26 May

      You are looking at a shuffling method here. This tutorial is about handling dice for role playing type games. You want this tutorial: /blog/2014/09/30/tutorial-how-to-shuffle-table-items/
      Shuffling randomizes a numerically indexed table. Then as you need items, you can just take them out of the table, one at a time based on the table’s new ordering. Insert them into your second table and when the second table is complete, shuffle that table and then use the values from that in the new table order.

  • kk
    Posted at 09:04h, 26 May

    Thanks a lot again and again.