Chapter 2: Writing Better Code

This chapter is all about using what we learned in the previous chapter and taking it to the next level. In this chapter, we will start by looking at functions. We will learn how and when to use them. Functions allow us to reduce the amount of duplicate code in our scripts. Once we have gained an understanding of this, we will then continue by looking at tables. Tables allow us to store a lot of data in just one variable. Once we have understood tables, we will start using loops. We will learn about all the different loops that Luau has. Besides that, we will use these loops in the tables that we make. Toward the end of the chapter, we will also explain modules. Modules allow us to write less duplicate code spread over multiple scripts.

In this chapter, we will cover the following topics:

  • Using functions
  • Storing data types in tables
  • Programming loops
  • Using modules
  • Exercises

By the end of this chapter, you will have learned about the fundamentals of programming in Luau. You will understand and learn more about functions, tables, loops, and modules and know why and when to use them. In addition, you will also know the best practices when using them.

Technical requirements

To start programming with Luau, you need access to a device with internet access. This can either be a Windows device or a Mac device. Additionally, you need to download the following software:

  • Roblox Player
  • Roblox Studio

All the code examples for this chapter can be found on GitHub at https://github.com/PacktPublishing/Mastering-Roblox-Coding.

The CiA video for this chapter can be found at https://bit.ly/3oxUMx7.

Using functions

In this section, we will start using something called functions. Let us start by finding out when to use functions. In Chapter 1, Getting Up to Speed with Roblox and Luau Basics, we saw the following code:

local MINIMUM_PLAYERS = 1
local MAXIMUM_PLAYERS = 8
local playerPosition = 1
-- Checking if the player's position is a valid number
if playerPosition >= MINIMUM_PLAYERS and playerPosition <= MAXIMUM_PLAYERS then
    
    -- Getting correct message based on player›s position
    if playerPosition <= 3 then
        print("Well done! You are in spot " .. playerPosition 
        .. "!")
    elseif playerPosition <= 5 then
        print("You are almost there!")
    else
        print("You are not in the top three yet! Keep going!")
    end
else
    -- The position of the player is not valid
    warn("Incorrect player position [" .. playerPosition .. 
    "]!")
end

The preceding code determines what message the user sees based on their position.

Now, let us say that, once this code runs, we want to change the position from first place to fourth place. Then, we want to get a new and different message based on their new position.

We can easily do this. In the preceding code example, below the last end, we can change the variable for the player position and copy and paste the entire if statement. Easy, everything works. Now, we want to change the player position again. We repeat the same process repeatedly until your script is 10,000 lines with the same if statement. Then, we realize that instead of having the same message for numbers four and five, we want to give them different messages. So, we have to change our if statement a few hundred times. That is going to be a lot of work.

In the following sections, we will learn how to solve this problem using functions. First, we will see examples of how functions are used and everything we can do with them.

Making a function

In this section, we will learn what a function is and what we need to do to make them. Functions contain code that we can reuse as many times as we want in the script. Therefore, we do not have to use the same code repeatedly. Instead, we call the function for it. First, let us make a function. The following code snippet shows you what a function looks like:

function printSomething()
    print("This was printed in a function!")
end

Let us analyze this function. A function starts with the function keyword. Then, we provide the name of our function. For functions, we also use the lower camel case naming method. After the name, we add parentheses, (). Similar to if statements and do statements, a new scope is made. For if statements, this scope is only executed if the result of the expression is true. Functions do not have expressions.

So, if we do not have expressions, how does the system know this code must be executed? Right now, if we run our script, nothing shows up in the console. That is because we never told the function to execute. The previous scripts we made were executed in the order of the lines. Functions only get executed when they are told to be. You can execute a function by putting its name followed by parentheses, as follows:

function printSomething()
    print("This was printed in a function!")
end
printSomething()

Now our code gets executed. Well done! We learned how to make our first function. In the next section, we will learn how to make our functions a bit more complex by using parameters and arguments in functions.

Tip

For readability reasons, a function might only have one purpose.

If a function is responsible for doing multiple things, the function should be split up into separate functions that call each other.

Parameters and arguments

In this section, we will start using parameters and arguments. But first, let us look at the racing example again. The entire if statement could be a function. This way, we prevent the previously described problem where we would have to copy and paste the entire if statement repeatedly:

local MINIMUM_PLAYERS = 1
local MAXIMUM_PLAYERS = 8
local playerPosition = 1
function givePositionFeedback()
    -- Checking if the player›s position is a valid number
    if playerPosition >= MINIMUM_PLAYERS and playerPosition <= 
    MAXIMUM_PLAYERS then        
        -- Getting correct message based on player›s position
        if playerPosition <= 3 then
            print("Well done! You are in spot " 
            .. playerPosition .. "!")
        elseif playerPosition <= 5 then
            print("You are almost there!")
        else
            print("You are not in the top three yet! Keep 
            going!")
        end
    else
        -- The position of the player is not valid
        warn("Incorrect player position [" .. playerPosition 
        .. "]!")
    end
end
givePositionFeedback()
playerPosition = math.random(1, 8)
givePositionFeedback()

Let us take a look at the preceding code. We get two different results in the Output frame when we run the code even though we only use the if statement once. This is because we made a function and called it twice. Other than that, the code is not very different. However, we have not seen the math.random(1, 8) part. Luau has a few built-in math functions that we can use. One of them is math.random(). What this does is that it takes a random number from between the two numbers you provided. In our case, we provided numbers one and eight. Therefore, the result of this function will be a number between those two values.

Now that we are talking about it, math.random() is a function. We call this function in a similar way to how we call our other functions. We say the name of the function and add parentheses at the end. However, for the math.random() function, we can give the function data inside the parentheses. On second thought, we have also seen this with the print() and warn() functions.

We can do this in our function, too. The data that we give inside of the parentheses is called arguments. When we receive this data inside our function, this same data is now stored in parameters.

Let us modify our current code so that playerPosition is no longer a global variable but a parameter:

Local MINIMUM_PLAYERS = 1
local MAXIMUM_PLAYERS = 8
function givePositionFeedback(playerPosition)
    -- Checking if the player's position is a valid number
    if playerPosition >= MINIMUM_PLAYERS and playerPosition <= 
    MAXIMUM_PLAYERS then
        
        -- Getting correct message based on player's position
        if playerPosition <= 3 then
            print("Well done! You are in spot " 
            .. playerPosition .. "!")
        elseif playerPosition <= 5 then
            print("You are almost there!")
        else
            print("You are not in the top three yet! Keep
            going!")
        end
    else
        -- The position of the player is not valid
        warn("Incorrect player position [" .. playerPosition 
        .. "]!")
    end
end
givePositionFeedback(math.random(1, 8))
givePositionFeedback(math.random(1, 8))

Tip

As a general rule, we keep the length of our function below 25 lines. This is to improve the readability of our code.

In the preceding code, we still see playerPosition; the only difference is that it is no longer a global variable. This is because it is a parameter now. The value of this parameter is defined when we call the function at the bottom of the script. Here, we use the math.random() method as an argument. This argument is going to be a value between one and eight. Our script can work with this.

We just saw how to give our function a parameter. But what if we wanted our function to have multiple parameters? In the next section, we will see how to give our function multiple parameters.

Multiple parameters

In this section, we will see that functions can have multiple parameters. We have seen this happen in the math.random() function. Parameters and arguments are separated using commas (,).

Let us make a simple calculator script by using functions and parameters. First, we will make a simple add function. This function is going to have two parameters. These parameters are going to be the numbers that it counts. For now, we will call these numbers x and y:

function add(x, y)
    print(x + y)
end
add(5, 5)

Here, we used two parameters. In the Output frame, you can see the sum of the numbers we provided as arguments.

But what if we accidentally give another argument when calling the function? In that case, our functions need two parameters: x and y. However, it gets three. Let us see what happens when we try to do the following:

function add(x, y)
    print(x)
    print(y)
end
add(1, 2, 3)

When we run the code, numbers 1 and 2 get printed, and number 3 gets ignored. This makes sense because we did not make a print for this argument. We can conclude that we can send as many arguments as we want to a function even if we do not use them without getting errors. Even though this is possible, you should not do it. Sending useless data to a function is a bad practice and creates extra work for no reason.

However, what happens when our function needs two parameters and only gets one? Take a look at the following code:

function add(x, y)
    print(x, y)
end
add(1)

Number 1 gets printed correctly. However, for the y parameter, nil is printed. We have seen nil before. In Luau, nil means nothing. For example, if we try to do a math operation with nil, we get an error. Of course, we do not want to create errors. In the next section, we will learn a method to ensure there are no parameters with the nil value.

Default parameter values

Sometimes, your function relies on having your parameters set. When parameters are not set, we can give them a default value. We can do this by using two things that we learned about earlier. In this section, we will learn how to make default parameter values by using if statements and setting new values to variables.

Let us take a look at the updated code:

function add(x, y)
    -- Default values for your parameters
    if x == nil then
        x = 0
    end
    if y == nil then
        y = 0
    end
    
    -- Function logic
    print(x)
    print(y)
end
add(1)

Now if we run our script, instead of nil being printed, the Output frame shows the default value, 0. Our if statement checks whether the parameter is not nil, and if it is, we change the parameter’s value to 0.

Because we do not specify that our parameter has to be a number either, we can bypass this if statement and still crash the code. Suppose we give a string instead of a number, our if statement would miss the wrong parameter. Luckily, we have something to get the name of the data type. The method to get this name is called typeof(). Let us improve our default data:

function add(x, y)
    -- Default values for your parameters
    if x == nil or typeof(x) ~= "number" then
        x = 0
    end
    if y == nil or typeof(y) ~= "number" then
        y = 0
    end
    
    -- Function logic
    print(x)
    print(y)
end
add(1, "2")

Now that we use the typeof() function, we can check whether the data type provided in the parameters is a number and not another data type such as a string. Our parameters are now fully protected from not having a set or having a wrong data type as a value.

These default parameters are a lot of work to implement. Therefore, it is custom to only implement these default parameter values when using your parameter as an Optional Parameter. An optional parameter is something that is not necessarily required for your function.

Another use case for default parameter values is when multiple developers use your function, and you are unsure how they will use it. We will talk more about this use case in the Using modules section.

Note

Sometimes, you can make an error somewhere in your script, and you do not get an error because you use default parameter values. In those scenarios, it might be challenging to find what is wrong with your script. After all, your default values are to prevent errors. However, you can tackle this by adding a warning message (warn()) when a default value is being used, as discussed in the Nested If statements section of Chapter 1, Getting Up to Speed with Roblox and Luau Basics.

Now that we understand how to make parameters and arguments, we can move on to the next cool thing we can do with functions: return types. In the following section, we will learn how to make our function return data.

Returning functions

In this section, we will start improving our calculator by adding return types to our functions. Let us continue developing our calculator. We already have an add() function. Let us make one that subtracts numbers. It looks pretty similar to the add() function. See the following code snippet:

function add(x, y)
    print(x + y)
end
function subtract(x, y)
    print(x - y)
end
add(5, 5)
subtract(10, 5)

Our code works, but we can do nothing with the generated data. What if we, first, use the add() function, and then subtract something from the number we just calculated the sum of? Somehow, we need the result of our previous operation. We can create a global variable that stores the result of the previous operation.

Creating a global variable for this is a Bad Practice. We try to avoid using global variables as much as possible. Usually, there is a much better alternative available. Functions can give back a result. We have already seen this happen with the math.random() function. We used this function as an argument. We were able to do this because this function returns a new number. Instead of printing the number, let us make the function return the result of the operation. To do this, we can use the return statement, as follows:

function add(x, y)
    return x + y
end
function subtract(x, y)
    return x – y
end
local sum = add(5, 5)
local difference = subtract(10, 5)

Using the return statement, we can use this method as an argument or store it in a variable, as shown in the preceding code. Now that we know how this works, we can make the previously described system. First, we can use the add() function, followed by the subtract() function that uses the result of the add() function. Our system looks like this:

function add(x, y)
    return x + y
end
function subtract(x, y)
    return x – y
end
local result = subtract(add(5, 5), 5)
print(result)

Now that we understand how to use return values, let us try to return multiple values from our functions. In the next section, we will see how to do this.

Multiple return values

Functions returning more than one value are not often seen, but they can be useful. For example, if we wanted to make a function that adds and subtracts and gives back both values using one function, we could do that. It looks like the following:

function add(x, y)
    return x + y
end
function subtract(x, y)
    return x – y
end
function addAndSubstract(x, y)
    return add(x, y), subtract(x, y)
end
local sum, difference = addAndSubstract(10, 5)

We added a new function, but there is nothing special about it. It is a normal function with two parameters. What is noteworthy, however, is that the return calls two functions: the add() function and the subtract() function. A comma separates the call of these functions (,). This separation with commas is something we saw when we added multiple arguments and parameters, too.

Nevertheless, how do we store both return values in a variable? Well, we do not. However, we can make two variables for each value. We have done this before. The only difference is a small shortcut when making these variables, which you can see in the preceding code example. Once again, we use the comma to have multiple variables in one line.

First, we use the local part that defines a new variable. Then, we have the variable’s name as we are used to. The only difference is that we have multiple variables separated by commas instead of one variable name. Once we have defined all our variable names, we place the equals (=) sign, as we are used to. Then, we set the value of the variable.

We did this in the preceding example, too. The only thing that might look confusing is that we only set one variable. It might look like we are doing that, but we are not. Indeed, we call one function, but because this function returns not one but two values, two variables get set.

Instead of having a function that returns two values, we could have just called both functions while defining our variable. It would have looked like this:

function add(x, y)
    return x + y
end
function subtract(x, y)
    return x – y
end
local sum, difference = add(10, 5), subtract(10, 5)

You would probably end up using the second option more often than making a function with two return values. However, in some scenarios, there is a real advantage to having multiple return values. We will see some of these advantages throughout the book, such as in Chapter 3, Event-Based Programming.

Either way, it is good to understand how multiple return values work in the case of a scenario where you work with a function that has it.

Now that we know all about return values, let us look at something else we can do with functions. Similar to if statements, we can nest functions, too. In the next section, we see how to nest functions.

Nested functions

In the previous sections, we learned a lot about functions. Nevertheless, did you know that a function is a variable? In this section, we will learn how to use this knowledge to make nested functions.

Previously, we defined functions using function functionName(). However, behind the scenes, Luau changes your function to a variable. Therefore, you can also make a function like this:

local printSomething = function(whatToPrint)
    print(whatToPrint)
end
printSomething("Hello World!")

If we write our function like this, we can give this variable to a function as an argument. Then, we can execute a parameter because it is a function.

You do not see this happen very often. However, because functions are technically a variable, can we have functions inside of functions? Yes, that is possible. Functions within functions are called Nested Functions.

But why would we use nested functions? Can we not just use two separate functions? Yes, you can use two separate functions. The only reason to use a nested function is to avoid making a new global function with the same parameters. There is one condition, though; you need to ensure the function you make as a nested function will never be required by another function. So, to prevent duplicate code, it makes no sense to have two nested functions that do the same thing.

Let us take a look at our calculator example again. Our calculator always needs to calculate the sum and the difference. In this case, we can turn our add and subtract functions into a nested function inside the addAndSubtract() function. It looks like this:

function addAndSubstract(x, y)
    -- Nested add function
    local function add()
        return x + y
    end
    
    -- Nested subtract function
    local function subtract()
        return x – y
    end
    -- Returning result
    return add(), subtract()
end
local sum, difference = addAndSubstract(10, 5)

Now that we turned our add() and subtract() functions into a nested function, we no longer need to give these functions parameters. Instead, we can simply get the parameter values from the addAndSubtract() function that is higher in the scope.

In a real calculator system, you would never see this. It might seem efficient to have the same function for all your operations and use the returned variable you want, but the opposite is true. Why perform math operations knowing you are not going to use the data?

Tip

Most of the time, it is good to avoid nested functions. Although, there are a few exceptions. In Chapter 3, Event-Based Programming, we will see some useful use cases for nested functions.

Now that we know all about functions, there is only one thing left for us to cover: best practices when using functions. These best practices are covered in the next section.

Best practices when using functions

In this section, we will cover another best practice when using functions. We have already seen some of the best practices you have to keep in mind throughout the previous sections. These ensure that our functions only have one purpose and prevent us from writing functions with more than 25 lines.

Besides the best practices we have just mentioned, there is another one. Now that we know how to use functions, we are no longer supposed to write our code outside of functions. One exception is the global variable. However, try to replace global variables with parameters as much as possible.

These functions still have to be called at some point. Make a setup() function that includes these calls when this is required. This way, you only have one function call at the bottom of your script.

Our calculator system should look like the following when using a setup function:

function setup()
    local sum = add(5, 5)
    local difference = subtract(10, 5)
    print(sum)
    print(difference)
end
function add(x, y)
    return x + y
end
function subtract(x, y)
    return x – y
end
setup()

Now that we understand these best practices, we have learned everything we need to know about functions.

In the previous sections, we learned how to make a function. Additionally, we learned about parameters and arguments in functions. They allow us to provide specific functions with specific data without using a global variable. Besides that, we have also seen how to give a function a return value. This allows us to use the outcome of a function for something else. Finally, we learned that functions are variables. Because of this, we were able to nest functions.

In the next section, we will introduce something new related to variables. The following section is all about tables.

Storing data types in tables

In this section, we will learn all about tables. We will learn what they are and what they are helpful for. To explain tables, we will take the example of a school system that stores data from its students.

You are probably aware that schools have data on their students. This data varies from your name, your class, and your test results.

Currently, we know of a way to store data. We can do that by using variables. So, let us make a school system that stores data.

For now, let us first focus on the variables:

local STUDENT_NAME_1 = "William"
local STUDENT_NAME_2 = "Sophie"

Currently, we have two variables for the first two students. An average class has about 20 students. Therefore, we would need 20 different variables for the first class alone. Now imagine this for an entire school. Having this many variables is very unorganized.

In the following sections, we will learn how to organize this data better by using tables.

Storing data in a table

There is a solution. Usually, when you start counting, as demonstrated in the preceding example, there is a better alternative to your variable names. This alternative uses a table; tables are a list of data. There is no size limit on this list.

We can use this table to store the names of our students. For example, our table could look like this:

local students = {"William", "Sophie"}

If we take a good look at the preceding code, we can see that it is not very different from something we saw earlier. Remember, in the Multiple return values section, we made a function that returned multiple values? Here, we had multiple variables on the same line. It looked something like this:

local student1, student2 = "William", "Sophie"

As you can see, it is almost identical. The primary difference is that the array only defines one variable (students), whereas the other example defines two variables (student1 and student2). Another difference, which is even smaller, is after the equals (=) sign. We have two strings separated by a comma (,). However, for the table, we have our values surrounded by curly brackets ({ and }).

If we want to add another student’s name to the table, we can simply place another comma (,) and place a new string behind it. We do not have to create another variable name as it is all stored in the students table.

Something unique about these tables is that you can place any data type in the same table. That means we can save data with different data types in the same table. Here is an example of a table that stores information about a student:

local studentInfo = {"James", 10284281, "5'9", true}

The preceding code is from a non-existing student. The first data in the table is the first name of the student as a string, followed by the student ID, which is a number. The school is also aware of the student’s height, which is stored as a string. Finally, we have a Boolean that determines whether the student is an active student or not.

In this section, we learned how to make a table. Besides this, we have compared a table to standard variables to see the advantages. But we already know how to read data from variables. So, how do we get the data from a table? In the next section, we will learn how to read data from a table.

Reading data from a table

When using variables, we can do whatever we want with the value by simply using the variable’s name. For tables, it works differently. If we print out the students table and open the Output frame, we should see something interesting. The following code snippet shows you how to print a table:

local students = {"William", "Sophie"}
print(students)

In the Output frame, the following appears:

{
    [1] = "William",
    [2] = "Sophie"
}

At first glance, the output and the given input might look different, but we will see many similarities if we look at the output again. For starters, we see the curly brackets, and we also see the students’ names. The only thing we do not see in our table is those weird numbers.

These numbers make more sense than you might think. They are the indexes of the values within the table. So, what are indexes? Let us take another look at the table. The first string in the table is William. Next, we see the number one in our Output frame, followed by the William string.

This index tells us your data’s position within the table. You can see this index as a variable name within a table. The index in the Luau tables starts at one and keeps counting up depending on the number of items within the table.

We can use this index to specify which item we want to print within the table. We do this by specifying the name of the table followed by brackets ([ and ]):

local students = {"William", "Sophie"}
print(students[1])

If this index data is not interesting for what you are trying to do, and you only want to print all the values within the table, you can use a built-in function called unpack():

local students = {"William", "Sophie"}
print(unpack(students))

In this section, we saw how to read data from a table. Besides that, we learned how the internal structure of a table works. Each piece of data gets a unique index. We use this index to get specific data from a table. In the following section, we will learn how to add new data to a table.

Setting data in a table

Adding a new item to an existing table is simple. There is a built-in function that has been written that does it for us. This built-in function is called table.insert(), and you can see it used in the following code snippet:

local students = {"William", "Sophie"}
table.insert(students, "Robbert")

The only new thing in this script is the table.insert() function. The name gives away what the function does. Your first argument of the table.insert() function is the table you wish to add your second argument into. If we print the table and look at the indexes, we see that student Robbert is now in index 3.

However, what if we accidentally put the wrong name into our table? Can we remove or update the data that is in a table? Yes, we can. We can use the table’s name and combine it with the value index we want to change. For example, we want to change Robbert, index 3, to Robert because we made a typo. Our script looks like this:

local students = {"William", "Sophie", "Robbert"}
students[3] = "Robert"

Knowing this, would it be possible to take an index, such as index 4, and set an item here? Essentially, that is what the table.insert() function does. Let us try to write our custom table insert function:

local students = {"William", "Sophie", "Robert"}
function setup()
    customTableInsert(students, "Emily")
    print(unpack(students))
end
function customTableInsert(table, newData)
    -- Getting index info
    local currentIndex = #table
    local newIndex = currentIndex + 1
    
    -- Setting new data
    table[newIndex] = newData
end
setup()

Let us take a look at the customTableInsert() function we made. Our first variable is the current index of the table. We get this index by putting a hashtag (#) in front of the table’s name. This hashtag is the length operator. We use this operator to get the length of the tables.

After that, we get the new index variable. The new index variable takes the current length of the table and adds one. For example, if we have three items in a table, our currentIndex variable would be 3. Therefore, adding one gives us 4. This index is not taken yet and adds a new value to our table.

If we want to remove someone from our table, we can update the index and set the value to nil. However, there is a much better alternative: a built-in function from Roblox. It is called table.remove(). The difference between setting the value to nil and using this function is that when you use table.remove(), all the indexes behind it get changed. Moving all the indexes prevents any empty indexes in your table. In comparison, setting the value to nil results in having an empty spot within your table. You can use the table.remove() function like this:

local students = {"William", "Sophie", "Robert"}
table.remove(students, 3)

In this section, we learned how to update existing data in our table. Besides that, we learned how to insert a completely new item into our table. Finally, we also learned how to remove data from our table using the table.remove() function.

If we understand all these things, we know the basics of tables. However, there is another cool thing we can do with tables. Currently, we work with indexes, but what if we could use strings as an index? Dictionaries allow us to do this. In the next section, we will take a good look at dictionaries.

Using dictionaries

Currently, we have a table with the names of our students. But what if we wanted to store much more information about each student? For instance, we want to store the first name, the student ID, the class name, and the number of times that the student was late. Our table could look like this:

local studentInfo = {
    "Lauren",
    12345,
    "H1",
    0
}

While this works, we do have a visual problem. Without reading the description of what we were going to make, you probably have no clue what any of these values mean. One solution could be to add comments before the value. However, Luau has something that can help us out here. It is called a dictionary.

Have you ever looked at a real-life dictionary? You look up a word, and it gives you the meaning of that word. Luau dictionaries are similar to this. Dictionaries have a key and a value that belongs to this key. This key can be a number, a string, or any other data type. You can see this key as an index of tables.

A dictionary of our student info could look like this:

local studentInfo = {
    name = "Lauren",
    id = 12345,
    class = "H1",
    ["times late"] = 0
}

At first glance, the meaning of each piece of data is instantly clear. Keep in mind that the times late key is written differently. It is surrounded by brackets ([ and ]) and quotation marks ("). The reason for this is because there is a space in between the words times and late. When using spaces, always put brackets and quotation marks around them.

Now, let us take a look at what happened to the indexes of our dictionary:

{
    ["class"] = "H1",
    ["id"] = 12345,
    ["name"] = "Lauren",
    ["times late"] = 0
}

As you can see, there are no numbers to identify which value belongs to which index. Our indexes are now strings. Because we are using a dictionary, we do not call them indexes but keys. Getting data from a dictionary looks simpler than getting data from a table:

local studentId = tableStudentInfo[2]
local studentId = dictionaryStudentInfo["id"]

Because you specify a key rather than an index between the brackets, it is instantly clear that you are getting the id key for this student from the dictionary without even looking at the variable’s name.

In this section, we learned how to use dictionaries. Previously, we also learned how to make tables. Similar to if statements and functions, we can also nest tables. In fact, we can also nest tables with dictionaries. Having nested tables is called a Multi-Dimensional table. In the following section, we will learn all about these multi-dimensional tables.

Multi-dimensional tables

Now we have two systems: one that contains each student’s information and one that contains all the names. The ideal situation would be to combine them. Right now, we have variables that contain all the names and variables that contain information about the individual students. We want one variable that contains all students and all student information.

For this, we can use multi-dimensional tables. So, what are multi-dimensional tables? So far, we have seen tables that contain data inside of them. However, it is possible to have tables that contain other tables. We can put our data into this second table.

This multi-dimensional table is the perfect solution for our current system. In addition, we can put a dictionary inside a table to make it even better. Let’s see what our system looks like if we use a multi-dimensional table:

local students = {
    {
        name = "William",
        id = 1,
        class = "H1",
        ["times late"] = 0
    },
    {
        name = "Sophie",
        id = 2,
        class = "H1",
        ["times late"] = 0
    },
}

The previous code snippet is what our multi-dimensional school system looks like. Let us look at the table to see whether we know the indexes and keys of our data:

{
    [1] = {
       ["class"] = "H1",
       ["id"] = 1,
       ["name"] = "William",
       ["times late"] = 0
    },
    [2] = {
       ["class"] = "H1",
       ["id"] = 2,
       ["name"] = "Sophie",
       ["times late"] = 0
    }
}

Now, we can see that the dictionary gets an index within the table. Inside this dictionary, there are keys for each data.

We have seen how to combine tables and dictionaries. While doing this, we made a multi-dimensional table. Having one has some advantages. In the next section, we learn how to get rid of redundant keys in favor of indexes.

Using indexes as IDs

Because the student info dictionary gets an index, does that mean our id key is unnecessary? Yes, this is an advantage you get when having a dictionary inside a table. Because each item in a table gets an index, you can use this index as an id key, which makes any custom id key redundant. For example, our code could look like this if we remove our id key:

{
    [1] = {
       ["class"] = "H1",
       ["name"] = "William",
       ["times late"] = 0
    },
    [2] = {
       ["class"] = "H1",
       ["name"] = "Sophie",
       ["times late"] = 0
    }
}

We learned how to use the index of data in a table to replace any id keys. Now that we know about this small optimization, we can continue to the next subsection. In the next subsection, we will learn how to read data from a multi-dimensional table.

Getting data from a multi-dimensional table

Previously, we saw how to get data from a table and a dictionary. Now, we have to combine them. Technically, we are going to combine three things that we previously learned. For starters, we will combine what we learned about getting data from tables with what we learned about references. With references, we referred to an object somewhere in the game, for example, somewhere in Workspace. However, this time, we have to reference something in a table. Take a look at the following code:

-- Reference to something in Workspace
workspace.Baseplate
-- Reference to something in a table
tableName[1]
-- Reference to our multi-dimensional table
students[1]["class"]

The first line we see is how we reference something in the Workspace. References are similar to multi-dimensional tables. First, we reference the Workspace, followed by a reference to the Baseplate. The difference is that we separate our children using dots (.), whereas, with tables, we use indexes surrounded by brackets ([]). You can see how we referenced something in a one-dimensional table on the second line.

Finally, we have our reference to a multi-dimensional table. First, we state the table’s name, followed by the index of the dictionary we want. Lastly, we define which key we want to reference within this dictionary.

Practice

Try making a multi-dimensional table and referencing a few indexes/keys.

Now that we understand how to reference data from a multi-dimensional table, it is time to learn how to update it. The following subsection explains how to do this.

Updating data in a multi-dimensional table

As you might have guessed, setting data inside a multi-dimensional table is the same as a normal table. You can refer to and set your new data as follows:

students[1]["Name"] = "John"

There is one new and noteworthy thing here. How are we supposed to add a whole new student to our table when the game is running? Well, the thought sounds more complex than the reality:

local students = {}
function addStudent(studentName, studentClass)
   table.insert(
      students,
      {
         name = studentName,
         class = studentClass,
         ["times late"] = 0
      }
   )
end
addStudent("Nicole", "H1")

We just use the table.insert() function. However, instead of having a data type as a second argument, this time, we set a whole table. As we learned earlier, Luau tables can contain all data types, including other tables.

Note

When we spoke about dictionaries, we mentioned keys being easier to read than indexes. Yet, we still use indexes for our students. While it is more challenging to read than if we chose to use the student’s name as the key, this has one major issue. Keys can only exist once per dictionary. There will almost certainly be students with the same name. With indexes, this is not an issue. There can be students with the same name because they get a different index.

Now that we know everything about multi-dimensional tables, we can move on to the last tables section. Previously, we learned that tables could contain all data types. However, what about functions? In the next section, we will learn all about functions in tables.

Functions in a table

In the Nested Functions section of this chapter, we learned that functions are variables. Additionally, we learned that you could store anything inside tables, including functions.

Previously, we made a simple calculator. Technically, this is something we can do inside a table, too. Take a look at the following code:

local calculator = {
   ["Add"] = function(x, y)
      return x + y
   end,
   Subtract = function(x, y)
      return x – y
   end
}

If we want to use the Add() function, first, we need to reference the function. Referencing the function works in the same way as referencing standard data in a table. Once we have referenced the function, we add parentheses that include the arguments we want to give it, as we are used to. Our code would look like the following:

local calculator = {
   ["Add"] = function(x, y) 
      return x + y
   end,
   Subtract = function(x, y) 
      return x - y
   end
}
print(calculator["Add"](5, 5))
print(calculator.Subtract(10, 5)) 

Now we know how to include functions in our tables, and we know all of the fundamentals and more. So far, they are great for storing data, but we still need to reference indexes to access something. In the next section, we will learn how to use loops. Of course, loops can be used for tables, too!

Programming loops

Quite a few times, we have made systems without taking them to the fullest, primarily because we missed knowledge of loops. Loops allow us to repeat a process multiple times without having to copy and paste the same code over and over again. This section explains everything you need to know about loops.

Here is a list of the three different loops that Luau has:

In the following sections, we will dive deeper into each of these loops.

while loops

The first loop we will look into is the while loop. As previously mentioned, a while loop keeps repeating the same code until a condition is met. Let us take a look at the following code:

function randomBoolean()
   return math.random(0, 5) == 0
end
function countTries()
   -- Counter variable
   local tries = 0
   
   -- While Loop
   while randomBoolean() == false do
      tries += 1
   end
   
   -- Prints the tries it took for the loop to end
   print("It took " .. tries .. " tries to end the loop.")
end
countTries()

Let us take a look at this code. The first function, randomBoolean(), is small but has multiple noteworthy items. It uses the math.random() function that is built into Roblox Luau. The math.random() function generates a random number between 0 and 5. Let us suppose this randomly generated number is 0. Then, true is returned. If not, false is returned.

Then, we have the countTries() function that includes a while loop. This function calls the randomBoolean() function as many times as possible until it returns true. If it returns true, the loop ends, and the print gets executed. If it returns false, we increment our tries variable by 1.

Wait, the randomBoolean() function has to return true for the loop to end? Why do we check whether it returns false then? Checking whether it returns false is not a mistake in the book! It might seem confusing at the start, so let us go over it. If the math.random() function picks 0, it returns true. If it does not, it returns false. We have false == false in our loop’s condition if it returns false. As we learned in the Using Conditionals chapter, false == false gives true. So, our while loop continues for as long as our condition equals true.

Because we use a while loop, our condition gets executed before our tries counter gets incremented. Having our condition executed first means that our tries counter remains 0 if the first execution returns true. This is because the code inside the while loop never gets executed. After all, the condition is never true because true == false gives false.

On the other hand, if we were to use a repeat until loop, the increment of tries would always be executed. A repeat until loop executes before checking the condition, whereas the while loop checks the condition before executing what is inside of the loop. In the next section, we will learn all about the repeat until loop.

repeat until loops

If we change the code from the previous section and use a repeat loop instead of a while loop, our code would look like the following:

function randomBoolean()
    return math.random(0, 5) == 0
end
function countTries()
    -- Counter variable
    local tries = 0
    
    -- Repeat Until Loop
    repeat
        tries += 1
    until
    randomBoolean() == true
    
    -- Prints the tries it took for the loop to end
    print("It took " .. tries .. " tries to end the loop.")
end
countTries()

The code for the preceding repeat until loop looks almost identical to the version that uses a while loop. It makes sense; both loops serve the same purpose. The real difference is that the tries variable is always equal to or greater than 1. No matter how many times you run your script, this will always be the case. This is because we use the repeat until loop. The repeat until loop executes its code before checking whether the condition is true.

This time, we check whether the randomBoolean() function returns true. It makes sense because if the randomBoolean() function returns true, we have the true == true condition, and the function stops. In comparison, when the function returns false, the condition would be false == true, which is false, resulting in the loop going again.

Practice

If this is the first time you have heard of these loops, they are probably really complex. Therefore, it is highly recommended that you practice with these loops.

A good exercise could be to recreate the previous while and repeat until loops without looking at the book’s code. That way, you experience what each loop does.

Now that we understand how the while and repeat until loops work, we can move on to the final loop Luau has to offer.

for loops

for Loops are the most common and, therefore, the most important to understand. There is only one difference between a for loop and other loops. The difference is that a for loop gets a fixed number of loops before starting. In comparison, the while and repeat loops can technically run forever.

Let’s take a look at the following code:

for i = 1, 10 do 
    print(i)
end

The preceding for loop prints the numbers 1 to 10 individually. Let us look at i first. This letter is a variable, which means you can name it anything. Usually, programmers take the i character as it is short for index. However, it is usually better to give it a name that better explains your variable. For example, this variable could also be named numberCounter.

Then, we see an equals (=) sign, followed by 1, a comma (,), and the number 10. If you run the code, you can probably guess what it does. The number 1 is your loop’s starting number; therefore, the i variable starts at one. The loop stops when the variable reaches the number 10. Every time the loop runs, the variable automatically gets incremented with one.

If we wanted to increase our variable by 2, we can add another comma (,) behind the 10 and specify what number we want to increase it by:

for numberCounter = 1, 10, 2 do
    print(numberCounter)
end

Now, we have changed our increment value to two. When we run our code, we get the following numbers in our Output frame: 1, 3, 5, 7, and 9. Then, it stops even though it never reached the limit we put it on. 9 is the smallest number that fits into 10 with our current increment value. It is good to know that the loop stops even when you are not hitting your limit explicitly.

But there is another for loop. This other for loop is mainly used to loop through tables:

local messages = {"Hello", "how", "are", "you"}
for i, v in pairs(messages) do
    print(i, v)
end

In the preceding example, we have a table with four strings. Then, we have a for loop that goes through them. First, we see i again. Once again, this variable serves as the index variable. However, this time, it is the current item’s index in the table. Then, we have the v variable. This is short for value. This variable contains the data that matches the current index within the table.

After the variables, we have pairs(messages). The pairs() function allows you to loop over a table and fills both the index and value variables. Once again, it is highly recommended that you find a fitting name for these variables instead. The thing with the pairs() function is that it is not guaranteed to give the results in the same order as our table. Not having the same order means getting the how string before the Hello string. If the order in which you are looping through the table is essential, you can use ipairs() instead. This function ensures Hello gets read before the how string.

However, you cannot always use the ipairs() function. If you are looping through a dictionary, the ipairs() function gives no result. Therefore, when looping over dictionaries, you should always use the pairs() function.

Tip

Sometimes, you are not going to use the index variable or the value variable. In those scenarios, it is custom to name it as an underscore (_) instead.

Now we know about the for loop, and we understand how to use all three loops in Luau. In the following sections, we will expand our knowledge of loops. In the following section, we will learn how to Break and Continue our loop.

Continuing and stopping a loop

Let us go back to the school system we had when we learned about tables. Our two-dimensional table looked like this:

local students = {
    {
        name = "William",
        class = "H1",
        ["times late"] = 0
    },
    {
        name = "Sophie",
        class = "H1",
        ["times late"] = 0
    },
}

Let us make a function that loops through students. Because we have a table, we can use a for loop. The index variable is the student’s ID, and the value variable is the dictionary that contains the student info. Knowing this, we can make a for loop.

We can look for the key named name inside the student info. Then, we can make an if statement to see whether the name in the dictionary matches the name we are looking for. Take a look at the following code:

local students = {
    {name = "William", class = "H1", ["times late"] = 0},
    {name = "Sophie", class = "H1", ["times late"] = 0},
}
function findStudent(studentName)
    -- Looping through students
    for studentId, studentInfo in pairs(students) do
        print("Current Student Id: " .. studentId)
        
        -- Getting the name of the current student
        -- that belongs to this student id.
        local currentStudentName = studentInfo["name"]
        
        -- Checking if student name matches the one
        -- we are looking for
        if currentStudentName == studentName then
            print("Found!")
        else
            print("Someone else.")
        end
    end
end
findStudent("William")

This code is an excellent start! There is just one problem with the preceding code. If we find the student we are looking for, the loop continues. However, we want our function to stop when the right person is found. Luau has something for this. It is called break. When you run the break statement in a loop, the loop stops.

Note

If the function that contains the loop has more code than the loop, it is still executed. This is the difference between the break statement and the return statement. The return statement makes both the loop and the function stop.

If we add the break statement, our code looks like this:

function findStudent(studentName)
    -- Looping through students
    for studentId, studentInfo in pairs(students) do
        print("Current Student Id: " .. studentId)
        
        -- Getting the name of the current student
        -- that belongs to this student id.
        local currentStudentName = studentInfo["name"]
        
        -- Checking if student name matches the one
        -- we are looking for
        if currentStudentName == studentName then
            print("Found! Stopping loop")
            break
        else
            print("Someone else, continuing.")
            continue
        end
    end
end

Note

Only the function is shown in the preceding code to reduce space. Replace this new function in the full version of the code.

Now that we are using the break statement, the loop will stop if we find whom we are looking for. As you can see, there is also a continue statement in the else part of your condition. When the continue statement is executed, the loop directly restarts even if something else was below the if statement.

If we want to modify our function and add a warning if you are looking for a student that does not exist, we can add a warn() function below the loop. As previously mentioned, the break statement only stops the loop, not the function.

So, if we want to implement this, we have to change our break statement into a return statement, as it stops the entire function, not just the loop. Then, we can add a warning below our for loop that nothing was found. After all, you can only reach this point if you do not reach the return statement:

function findStudent(studentName)
    -- Looping through students
    for studentId, studentInfo in pairs(students) do
        print("Current Student Id: " .. studentId)
        
        -- Getting the name of the current student
        -- that belongs to this student id.
        local currentStudentName = studentInfo["name"]
        
        -- Checking if student name matches the one
        -- we are looking for
        if currentStudentName == studentName then
            print("Found! Stopping function")
            return
        else
            print("Someone else, continuing.")
            continue
        end
    end
    -- student was not found
    warn("Student [" .. studentName .. "] does not exist.")
end

The preceding code is the updated function for our new system. Try it for yourself.

This section taught us how to use the continue and break statements for loops. We have also seen a use case where we use the return statement instead of the break statement. In the following section, we will learn how to deal with large loops without making our system lag or crash.

Large oops

Sometimes, the loop you have runs forever or runs complex code many times. You will notice that your Roblox Studio stops responding if you do this. Usually, Roblox automatically stops your loop after a while, but this is not always the case.

Either way, this is not ideal. However, we have a solution. We can use the task.wait() function. This function pauses your current thread until the specified time is over:

while true do
    print("Loop is running.")
    task.wait(1)
end

The preceding code prints Loop is running every second. The task.wait(1) function gets the parameter of 1. This specifies the duration it has to pause the thread. Now if we want to stop our loop for 2 seconds, we can simply change 1 to 2. If we remove the parameter altogether, the wait pauses the script for a really short time. If you want to see exactly how long this takes, you can execute the following code:

print(task.wait())  

This code prints the exact duration it paused the thread for.

Now we know how to pause our thread. Additionally, we know how to prevent our desktop from lagging. Therefore, we know all the fundamentals of loops.

Previously, we learned about three different loops for Luau. The while, repeat... until, and for loops. We learned what each loop is used for. Then, we learned how to break out of the loops and restart the loop for the next interval by using the continue statement and the break statement. Finally, we also learned how to pause threads for a certain period to prevent our system from lagging when having large loops.

In the next section, we will learn about modules. Previously, we learned how functions prevent duplicate code in our script. Modules do the same. However, they prevent duplicate code being spread over multiple scripts instead of inside one script.

Using modules

So far, we have learned a lot about programming. First, we learned that programming is all about data. Then, we learned about many ways to optimize our code, such as using variables, conditionals, functions, tables, and even loops. All of these ensure we can write our code more efficiently without copying the same thing repeatedly.

There is one small optimization left for us to cover. The items we previously listed are all optimizations within the same script. Sometimes, we want systems to use another system. So, how do we transfer one particular script’s data to another script?

In the following sections, we will learn how to use modules. Modules are their own “script” but can be required in another script. This way, things that the module knows can be used in your other script. What you just read probably sounds complex, so let us continue our student system to explain all of this in more detail.

Creating a module script

First, we need to figure out what we want to make. Let us think about the systems a school might have. For example, schools give their students grades. Grades are something we can add to our student’s table. Schools giving their students grades can be a unique system. Another system might keep track of the student’s presence during classes. Both systems need access to the student’s table. Therefore, the system with the student’s table can be a module. We will start by implementing a module.

Because modules are not scripts, we have to make a new ModuleScript. You can do this in the same way you make scripts. However, this time, do not select Script but make a ModuleScript. Place the ModuleScript in the ServerStorage service instead of the ServerScriptService service.

The default code of a module script looks like this:

local module = {}
return module

What’s unique about a module is that it needs to have a table variable. The default table variable is named module, but you can name the variable anything. It is custom to rename your variable to the same name as your ModuleScript. At the end of the module, you have to return this table. Returning this variable has to be the last thing you do in your module:

local StudentSystem = {}
local students = {
    {
        name = "William",
        class = "H1",
        ["times late"] = 0,
        grades = {}
    },
    {
        name = "Sophie",
        class = "H1",
        ["times late"] = 0,
        grades = {}
    },
}
function StudentSystem:GetStudentInfo(studentName)
    -- Looping through students
    for _, studentInfo in ipairs(students) do
        -- Getting the current student›s name that belongs to
        -- this student id.
        local currentStudentName = studentInfo["name"]
        -- Checking if student name matches
        if currentStudentName == studentName then
            return studentInfo
        end
    end
    -- Student was not found
    warn("Student [" .. studentName .. "] does not exist.")
end
return StudentSystem

We have added our student’s table and our :GetStudentInfo() function. However, there is one noteworthy change in the code. This change has to do with the naming of our function. The table variable that we return is specified in the function’s name. We do this to ensure this function can be used when another script uses this module. If we forgot this, our function would still not be accessible in another script.

Now that we know how to make a ModuleScript, and how to make functions that can be accessed from other scripts, we can move on to the next section. The following section explains how to require this module from another script.

Requiring the module from another script

Now, let us make a normal script in ServerScriptService. First, we need to require this module. The module is called StudentSystem and can be found in ServerStorage. We need a reference to the ServerStorage service, and then we need to call require() on the module. We can do that in the following way:

local ServerStorage = game:GetService("ServerStorage")
local StudentSystem = require(ServerStorage.StudentSystem)

Note

Notice how both variables start with an uppercase character instead of a lowercase character? This naming method is allowed because services and modules are excluded from the lower camel case naming method and use the upper camel case naming method.

Now, we have included the StudentSystem module in our script. If we want to call a function from this module, we state the variable’s name, StudentSystem, and then add a colon (:) followed by the function’s name and parentheses:

print(StudentSystem:GetStudentInfo("William"))

Extra Exercise

If you wish to practice more with modules, make a function to add a number in the student’s grades table. Use the table.insert() function for this. To keep it simple, use numbers that range from 1 to 10 instead of letters like A.

Another good exercise is to make a function that calculates the student’s average grade. You can use loops to calculate the sum of grades and then divide it by the student’s number of grades.

Example answers to both exercises can be found on the GitHub page of this book:

https://github.com/PacktPublishing/Mastering-Roblox-Coding/tree/main/Exercises

Now that we understand how modules work, we have finished everything the second chapter offers. In the following section, we will practice with the information we have learned.

Exercise 2.1 – simple elevator

In this exercise, we will create a simple elevator to enable us to practice more with loops. We will create a simple part that we will move upward. When players stand on this part, they will automatically go up, resulting in an elevator effect happening.

To create our elevator, follow these steps:

  1. Open Roblox Studio and create a new Baseplate.
  2. Create a new part in the Workspace named Elevator.
  3. Create a new script in ServerScriptService.
  4. Make a reference to the Elevator part in the Workspace.
  5. Create a loop that increases the height of the part by changing the Position property of the part. Once the loop is done, the part should be 100 studs higher. If the original position was {0, 0, 0}, then the new position should be {0, 100, 0}. The position should increase every second.

Tip for 5: If you cannot figure out which loop to use, read the Programming loops section again. Our elevator should increase its height by 100 studs which is a previously set amount.

Play the game and check whether the Elevator part goes up correctly. Make sure the part does not go up endlessly. If this is the case, you might have chosen the wrong loop. If the Output frame displays any errors, try to fix them. An example answer to this exercise can be found on the GitHub page for this book:

https://github.com/PacktPublishing/Mastering-Roblox-Coding/tree/main/Exercises

Exercise 2.2 – converting loops

In this exercise, we will convert a functioning while loop into a repeat loop. Both loops are very similar. In the Programming loops section, we explained the difference between both. Take a look at the following code snippet:

local spawnLocation = workspace.SpawnLocation
while true do
    spawnLocation.Orientation += Vector3.new(0, 1, 0)
    task.wait()
end

This code snippet rotates SpawnLocation in the Workspace. Feel free to run this script before you convert it.

Exercise:

  • Convert the preceding code snippet into a repeat loop.
  • What is different about the condition of the repeat loop compared to the while loop, and why?

Play the game and make sure the spawn location rotates. If the Output frame displays any errors, try to fix them. An example answer to this exercise can be found on the GitHub page for this book:

https://github.com/PacktPublishing/Mastering-Roblox-Coding/tree/main/Exercises

Exercise 2.3 – Police System II (difficult)

This exercise continues the same system idea as Exercise 2.2 – converting loops. However, the code is from scratch. In addition, the system is now updated with our newly learned optimizations.

System description:

The police want a system that calculates fines for speeding tickets. The officer wants to provide a list that includes the following: the speed at which the driver was going, whether the driver has a license, and whether the driver was driving recklessly or not. Sometimes, the officer forgets to include something in this list. As a result, the driver might not get a ticket for the crime. Forgetting to include something can be used to exclude certain crimes, too. There should be a list that contains each crime that can be committed. You can find the price of each ticket if the driver commits the crime in each sub-list. Besides the price, there should also be a unique function for each crime that can be used to check whether the driver committed this specific crime or not. A warning should be put into the system’s Output frame if the ticket price or the unique function is missing. If the driver did not commit a crime, the ticket price would be zero. The ticket price should be displayed in the Output frame with the following text: Ticket Price: 0. The number depends on the height of the ticket.

Let us analyze the system description together. We see the following things:

  • “The police wants a system that calculates fines for speeding tickets.” -> This could be a function named calculateTicketPrice.
  • “The police wants to provide a list that includes the following: the speed at which the driver was going, whether the driver has a license, and whether the driver was driving recklessly or not.” -> This could be a dictionary that contains the required items. This dictionary can be given to the previously named function as a parameter.
  • “There should be a list that contains each crime that can be committed.” -> A global table/dictionary that contains all crimes.
  • “In each sub-list, you can find the price of each ticket if the driver committed the crime.” -> This global table/dictionary should be multi-dimensional. The sub-table for each crime should contain the ticket price.
  • “There should also be a unique function for each crime that can be used to check whether the driver committed this specific crime or not.” -> There can be a function within each sub-table per crime. For example, this function could be named isViolating and that receives the previously received data as a parameter.
  • “Sometimes, the officer forgets to include something in this list. As a result, the driver may not get a ticket for this crime.” -> Each unique function should check whether the data it receives includes the required data to check.
  • “If the ticket price or the unique function is missing, a warning should be put into the system’s output.” -> We can make a function to check whether all the required data is within the sub-table for the specific crime.
  • “The ticket price should be displayed in the Output frame.” -> For this, we can use the print function.

Note

Because this is a complex system, in this exercise, you only have to fill out certain system parts. Either way, try to understand the given structure. If you have another idea in mind, feel free to try it. There are a million possible solutions for this system.

Get the fill in code from the GitHub page of this book: https://github.com/PacktPublishing/Mastering-Roblox-Coding/tree/main/Exercises.

Exercise:

  1. Create the isViolating() function without checking whether the required data is present.

Tip for 1: Use simple if statements or expressions to return a Boolean.

  1. Update the isViolating() function to check whether all the data you use from the data parameter is present. If not, return false.
  2. Create a loop in the calculateTicketPrice() function that loops through the crimeSystem table.
  3. Update the isRequiredCrimeDataPresent() function to return false, and give a warning if the ticketPrice variable is not present in the crimeData parameter dictionary.
  4. Update the isRequiredCrimeDataPresent() function to return false, and give a warning if the isViolatingFunction variable is not present in the crimeData parameter dictionary.
  5. Make an if statement that calls the isRequiredCrimeDataPresent() function in the loop you created in step 3. If false is returned, continue to check the next crime.
  6. Call the isViolating() function in the value variable of your loop. If true is returned, increment the ticket price.

Test your system by changing a few variables and check whether the correct result is being displayed. Try to fix any errors that could show up in the Output frame. An example answer to this exercise can be found on the GitHub page for this book:

https://github.com/PacktPublishing/Mastering-Roblox-Coding/tree/main/Exercises

Summary

Sometimes, we repeat the same piece of code multiple times. Instead of copying and pasting, we can use functions. We learned that functions allow us to repeat the same code multiple times. Additionally, we saw that functions can have parameters. We saw how these parameters could change the function’s behavior depending on what conditionals you have. Besides that, we also learned that functions could return data by using the return statement. We use the lower camel case naming method, which is similar to variables for function names. We learned how to give the function a fitting name. Additionally, we learned how to split functions if functions have multiple purposes. The same applies when our function gets more than 25 lines. These are all for readability reasons.

Another great way to reduce duplicate code is by using loops. First, we saw how loops repeat the same code a few times. Then, we learned that there are three different loops. We saw that while and repeat loops could run indefinitely, and they only stop when a particular condition is met. In contrast, for loops receive a fixed number of runs until they can stop. We learned that adding a break statement or a return statement makes it possible to break out of this loop. Finally, we learned that for loops are the most common loops in Luau. They are also used when looping through the data in a table.

The last optimization we learned was how to reduce duplicate code over multiple scripts. For this, we used modules. For example, if multiple scripts require the same data or functions, they can be turned into a module. We saw how different scripts could require this module, resulting in you not making the same function multiple times.

Additionally, we learned that there are tables in Luau. We saw how tables allow us to store data in a list. We saw how you could keep specific data connected by having a multi-dimensional table. Each piece of data in a table gets a unique index. We learned that this index is a simple number that keeps counting up. Because these numbers can sometimes be vague, we started using dictionaries. Dictionaries have keys instead of indexes. These keys can be numbers. However, they can also be strings. You can name them whatever you want. Because you can name them anything you want, we immediately noticed an increase in our table’s readability without having to explain what each piece of data does.

In this chapter, we learned how to write code more efficiently. In the next chapter, we will start by optimizing our code. We will learn how to use events in combination with everything we learned previously. These events allow us to prevent our code from running when required.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.23.101.60