As your programs become more sophisticated, you might want to reuse code you’ve already written to save time. Fortunately, you can reuse your code as a module, which is a program that contains functions that other programs can call.
In this chapter, you’ll learn how to create custom functions and your own module. The turtles will need to run these functions in Chapter 8, so you’ll write functions your tree farmer turtles (and the turtles you make in later chapters) will load as a module.
To create your own functions, you use a function statement, which is made up of the function keyword followed by the name of the function and a set of parentheses. All the code following the function statement and prior to an end statement is part of the function.
Let’s look at how a function works by writing one. Create a new program by running edit hellofunction and entering the following code:
hellofunction
1. print('Start of the program.')
2.
3. function hello()
4. print('Hello, world!')
5. end
6.
7. hello()
8. hello()
9. print('End of the program.')
Then run hellofunction from the command shell. The output will look like this:
> hellofunction
Start of the program.
Hello, world!
Hello, world!
End of the program.
Line 1 prints Start of the program. and isn’t part of a function. Line 3 creates the hello() function, but notice that the hello() function doesn’t run here and the execution skips to line 7 instead.
The code in a function doesn’t run when you create the function. It runs only when the function is called. You call a function in the program by using its name followed by a set of parentheses. This syntax tells the program to execute the code contained in the function. We call the hello() function on line 7, which makes the execution move to line 4 where the function was created. Line 4 prints Hello, world!.
When the function call ends, the program returns to the line that originally called the function and continues on. In this case, when the execution reaches the end statement of the hello() function, it returns to line 7 where it was called and then moves to line 8. Line 8 also calls hello(), which makes the execution return to the function on line 4 and print Hello, world! again. Then, when the execution reaches the end of the hello() function, it returns to line 8. Finally, line 9 prints End of the program. and the program terminates.
The hello() function is simple, but it isn’t any more convenient than writing two print() calls. However, as your programs become more complex, organizing your code into functions will become more useful, especially when you begin using arguments.
We can send values to a function when we call the function. These values are known as arguments. The arguments are assigned to variables known as parameters. The parameters are used inside the function just like variables.
To see how arguments and parameters work, create a new program by running edit sayhello and entering the following code. This program tells the turtle to print a message to greet another turtle:
sayhello
1. function sayHello(name) -- name is a parameter
2. print('Hello, ' .. name)
3. end
4.
5. sayHello('Artemisia') -- 'Artemisia' is an argument
6. sayHello('Elisabetta') -- 'Elisabetta' is an argument
In this program, the name variable in the sayHello function statement on line 1 is a parameter. The strings 'Artemisia' and 'Elisabetta' on lines 5 and 6 are arguments.
Line 1 creates the sayHello() function, but sayHello() runs only when it’s called, so the execution skips to line 5. Line 5 is a function call to the sayHello() function. When we use an argument in a function call like this, we say we are passing the argument to the function call. Line 5 passes 'Artemisia' as an argument to sayHello(), which makes the execution move to the sayHello() function on line 2. Then, the name variable, which is the function’s parameter, is set to the argument it was passed, which is 'Artemisia'. The name variable is used in the print() function call on line 2, so the program prints Hello, Artemisia.
When the execution reaches the end of the sayHello() function, it returns to line 5 and then moves to line 6. Line 6 calls sayHello() again, but this time passes 'Elisabetta' as an argument, which makes the execution set name to 'Elisabetta'. The execution moves to line 2 and prints Hello, Elisabetta. Then the execution reaches the end of the sayHello() function and returns to line 6. Because there are no more lines of code, the program terminates.
Run the sayhello program on your turtle. It will display the following:
> sayhello
Hello, Artemisia
Hello, Elisabetta
The sayHello() function will print different strings depending on the argument passed to it.
Function calls evaluate to a return value, which you can use like any other value. As a result, you can use function calls in an expression anywhere you would use a value. For example, enter the following into the Lua shell.
lua> math.random(1, 6) + 1
5
The function call math.random(1, 6) returns a random value from 1 to 6. In this example, that return value is 4, so this expression evaluates to 4 + 1, which then evaluates to the value 5. (You probably got a different number since math.random() is random, after all.)
When creating your own function, you specify the return value using a return statement. A return statement is made up of the return keyword followed by a value or expression. To see how return statements work, create a new program by running edit givecandy and entering the following code:
givecandy
1. function candiesToGive(name)
2. if name == 'Al' then
3. return 10
4. end
5.
6. return 2
7. end
8.
9. lavCandy = candiesToGive('Lavinia')
10. alCandy = candiesToGive('Al')
11. print('Lavinia gets ' .. lavCandy .. ' pieces')
12. print('Al gets ' .. alCandy .. ' pieces')
The candiesToGive() function returns either the value 10 or 2 depending on what is passed in for its name parameter. When 'Al' is passed to the function, the if statement in the function is true, which makes the execution move to line 3 and return 10. When any other value is passed to the function, the if statement’s comparison is false, so the execution skips ahead to line 6, making the function return 2. You can think of arguments as the input to a function and return values as the output.
On line 9, when 'Lavinia' is passed to candiesToGive(), the function returns 2, which means the variable lavCandy is assigned to 2. On line 10, alCandy is assigned to the value 10, which is returned by candiesToGive() when 'Al' is passed as an argument. The variables lavCandy and alCandy can be used in a print() function call, as on lines 11 and 12, because they both store return values.
When you run this program, the output looks like this:
> givecandy
Lavinia gets 2 pieces
Al gets 10 pieces
Instead of typing functions into every program that uses them, you can just type functions into a module once, and then every program that loads the module can use them. This is called code reuse.
You create modules with the edit program the same way you create programs, and then you can import the modules into other programs. In this chapter, you’ll learn how to create a module, and in the next chapter, you’ll learn how to use modules in other programs. We’ll create the hare module now (as in the fable “The Tortoise and the Hare”) and use it to give all our turtles some handy utility functions.
Create the hare module by running edit hare in the command shell and entering the following code:
hare
1. --[[Function Module program by Al Sweigart
2. Provides useful utility functions.]]
3.
4. -- selectItem() selects the inventory
5. -- slot with the named item, returns
6. -- true if found and false if not
7. function selectItem(name)
8. -- check all inventory slots
9. local item
10. for slot = 1, 16 do
11. item = turtle.getItemDetail(slot)
12. if item ~= nil and item['name'] == name then
13. turtle.select(slot)
14. return true
15. end
16. end
17.
18. return false -- couldn't find item
19. end
20.
21.
22. -- selectEmptySlot() selects inventory
23. -- slot that is empty, returns true if
24. -- found, false if no empty spaces
25. function selectEmptySlot()
26
27. -- loop through all slots
28. for slot = 1, 16 do
29. if turtle.getItemCount(slot) == 0 then
30. turtle.select(slot)
31. return true
32. end
33. end
34. return false -- couldn't find empty space
35. end
This module is 35 lines long, but you only have to type it once. Without this module, you would have to type this code in every program you want to use it in. Modules save you a lot of time! In the next section, we’ll experiment with the hare module’s functions in the Lua shell.
If you get errors when running this program, carefully compare your code to the code in this book to find any typos. If you still cannot fix your program, delete the file by running delete hare and then download it by running pastebin get wwzvaKuW hare. (Note that the hare module you download from pastebin contains all the functions added to it in the book, not just the ones added in this chapter.)
The hare module contains two functions: selectItem() and selectEmptySlot(). The selectItem() function selects an inventory slot containing a given item, and the selectEmptySlot() function selects the first inventory slot it can find that has nothing in it. Both functions are useful when you’re working with a turtle’s inventory.
Lua’s built-in modules (such as the math or os modules) are automatically loaded for all turtle programs, but you need to load the modules you create by calling the os.loadAPI() function. Note that ComputerCraft also calls modules application programming interfaces (APIs). To use os.loadAPI(), pass the name of the module you want to load as a string value. For example, you’ll need to run os.loadAPI('hare') to use the hare module’s functions in the rest of your program.
The os.loadAPI() function makes the program’s functions available to the calling program. The function returns true if the module was found and run. It returns false if the module doesn’t exist—for example, if you made a typo, such as os.loadAPI('har'). In the next section, you’ll load the hare module and call some of its functions.
To see how the selectItem() function works, open the turtle’s GUI and put oak logs and several other items in the turtle’s inventory, as shown in Figure 7-1.
Then, open the Lua shell and enter the following code to select the slot containing the logs:
lua> os.loadAPI('hare')
true
lua> hare.selectItem('minecraft:log')
true
The first line of code loads the hare module, and the second line of code selects wooden log blocks in the turtle’s inventory by passing the string 'minecraft:log' to selectItem().
Figure 7-1: The turtle’s GUI contains oak logs and various items with slot 1 as the current slot.
The 'minecraft:log' string is the Minecraft name ID for oak logs. A name ID is a unique string beginning with 'minecraft:' that Minecraft uses to identify blocks and inventory items. You can find a list of name IDs in “List of Block Name IDs” on page 219. In “Getting Item Details from a Slot” on page 80, you’ll learn how to get an item’s name ID with the turtle.getItemDetail() function instead of having to look it up.
When you look at the turtle’s inventory, notice that the wooden log block is currently selected, as shown in Figure 7-2. The hare.selectItem() function can find and select the log no matter which inventory slot it’s in. Try moving the log to a different slot and running hare.selectItem('minecraft:log') again. The slot the log is in will be selected again, as indicated by the thick border around the slot.
Figure 7-2: The slot with the log is selected after running hare.selectItem('minecraft:log').
The selectEmptySlot() function selects the first inventory slot it can find that has nothing in it. This function is useful when you’re about to mine blocks, because you need an empty slot to store the mined blocks in. In the Lua shell, run the following code:
lua> os.loadAPI('hare')
true
lua> hare.selectEmptySlot()
true
After calling hare.selectEmptySlot(), the first empty slot will be selected, as shown in Figure 7-3.
Figure 7-3: The first empty slot is selected after calling hare.selectEmptySlot().
The tree-farming program in Chapter 8 will use the two functions selectItem() and selectEmptySlot(), but they could be useful in many turtle programs. Let’s look at the code behind them.
The first part of the hare module contains comments for the program and additional comments that explain what the selectItem() function does.
hare
1. --[[Function Module program by Al Sweigart
2. Provides useful utility functions.]]
3.
4. -- selectItem() selects the inventory
5. -- slot with the named item, returns
6. -- true if found and false if not
7. function selectItem(name)
The function statement for the selectItem() function is on line 7. Remember that the code in the block that follows the function statement is executed whenever the function is called. The code in the block doesn’t run when the function statement is first encountered. To understand the function code in selectItem(), you first need to learn about certain functions that are used to look at the turtle’s inventory.
Each turtle has an inventory of 16 slots, numbered as shown in Figure 7-4.
Figure 7-4: The numbered inventory slots of a turtle
Turtles have three built-in functions that allow you to interact with the turtle’s inventory using the slot numbers. These functions are called turtle.select(), turtle.getItemCount(), and turtle.getItemDetail(). Let’s look at how these functions work in more detail.
You can change the current slot using the turtle.select() function by passing the number of the slot you want to select to the function. See the slot numbers shown in Figure 7-4. Enter the following lines into the Lua shell to see how this function works:
lua> turtle.select(2)
true
lua> turtle.select(16)
true
After you call the function, notice the thick border that indicates the current slot change. Many other turtle functions use the current slot, so knowing how to change it in your programs is useful when you need to interact with a specific slot or item in a slot.
If you want to know how many items are in an inventory slot, call the turtle.getItemCount() function and pass it a slot number. Enter the following lines into the Lua shell:
lua> turtle.getItemCount(1)
1
lua> turtle.getItemCount(16)
0
If you don’t pass any argument to turtle.getItemCount(), the function checks the currently selected slot. This function is straightforward and returns the number of items in an inventory slot, but it doesn’t tell you anything about what is inside the inventory slot. To learn the details about what is stored in the slot, you need to use the turtle.getItemDetail() function.
You can detect the specifics of what is in the turtle’s inventory by calling the turtle.getItemDetail() function and passing the function the slot number you want to check. Put a wooden log inside inventory slot 1, and then enter the following code into the Lua shell:
lua> turtle.getItemDetail(1)
{
count = 1,
name = "minecraft:log",
damage = 0,
}
The value that turtle.getItemDetail() returns indicates that one log is in inventory slot 1. If nothing is in that inventory slot, turtle.getItemDetail() returns nil. If you don’t pass any arguments to turtle.getItemDetail(), it returns information about the currently selected slot.
The returned value looks completely different from the other values you’ve seen so far because it’s a table value.
Table values can hold multiple values. Similar to how strings begin and end with quotes, values of the table data type begin and end with braces, { }. The values that a table value holds are organized in key-value pairs, which are listed and separated by commas. Enter the following line into the Lua shell to create a new table value and see what these pairs look like:
lua> myStuff = {logs=5, stone=4, arrows=10}
This line assigns the table value {logs=5, stone=4, arrows=10} to the variable myStuff. The three number values in this table—5, 4, and 10—are the value part of the key-value pairs. They can be identified by their respective keys: 'logs', 'stone', and 'arrows'. Together, a key and a value form a key-value pair, such as logs=5, with the key first, followed by an equal sign (=), and then followed by the value. Keys and values can be almost any data type, so a key can be an integer or a value can be a string. The only exception is that a table key cannot be the nil value.
To access the individual values inside the table, enter the variable myStuff followed by the key inside square brackets, [ ]. This statement will evaluate to the value in the table associated with that key. For example, enter the following into the Lua shell to access a value from the table we created earlier:
lua> myStuff['logs']
5
lua> 'I have ' .. myStuff['stone'] .. ' stone.'
I have 4 stone.
lua> 'I need ' .. (12 - myStuff['arrows']) .. ' more arrows to have a dozen.'
I need 2 more arrows to have a dozen.
The first line of code evaluates to the value associated with 'logs', which is the number 5. Because the expression evaluates to a value, you can use it anywhere you would normally use a value, such as when concatenating strings or when doing math (if the value is a number).
Let’s look at the turtle.getItemDetail() function again. Put one wood log in inventory slot 1, and then enter the following into the Lua shell:
lua> item = turtle.getItemDetail(1)
lua> item['name']
minecraft:log
lua> item['count']
1
When you enter item['name'], you get the value at the key name in the table, which is minecraft:log. You can retrieve other values, such as the value associated with the key count, too. Now you have a way to get the turtle’s inventory information as string and number values! Notice that if turtle.getItemDetail() returns nil because nothing is in that inventory slot, trying to use the square brackets will result in an error:
lua> item = turtle.getItemDetail(16)
lua> item['name']
lua:1: attempt to index ? (a nil value)
To prevent this error from happening in your programs, make sure item is not set to nil by using an if statement. Although we won’t use this code in this chapter’s program, if you need to do this in a future program, you can use the following code:
local item = turtle.getItemDetail(16)
if item ~= nil then
print(item['name'])
end
With this code, item['name'] runs only if item is not equal (~=) to nil.
The selectItem() function will find an item in the turtle’s inventory that corresponds to the string passed to the function’s name parameter. This function first declares a local variable named item on line 9. This local variable only exists inside the selectItem() function and cannot be used elsewhere in the program.
hare
7. function selectItem(name)
8. -- check all inventory slots
9. local item
You’ve already learned about the local keyword in “The Boolean Data Type” on page 49, but you haven’t yet learned about scope. A scope is a part of the program where a variable is visible, and local variables exist only inside one scope. Lua has two types of scopes: global and local.
Variables declared in the global scope are declared outside of any function and are visible in all parts of the program. This can cause problems if you accidentally reuse a variable name because the program will use any previously stored values in the variable or will allow functions to overwrite a variable with a new value. To address this issue, you can make variables in local scopes, which you create every time you make a function. When a variable is declared local to a function, it will exist only in that function’s local scope and will cease to exist once the function returns.
By using local variables, you can have two variables with the same name as long as they exist in different scopes. Because you’ll add more functions to the hare module in the future, we’ve declared item as local to the selectItem() function. Unless you need a variable that can be accessed throughout the entire program, it’s best to declare variables as local in every function to avoid future problems.
Variables used in a for loop, such as the i variable in for i = 1, 4 do, are local to that for loop. These variables will not exist outside the for loop, just like the variables local to a function will not exist outside the function.
After we’ve set up our variables, the program creates a variable named slot that is local to the for loop on line 10.
hare
10. for slot = 1, 16 do
11. item = turtle.getItemDetail(slot)
12. if item ~= nil and item['name'] == name then
13. turtle.select(slot)
14. return true
15. end
16. end
17.
18. return false -- couldn't find item
19. end
The slot variable in for slot = 1, 16 do will not exist after the loop’s end statement. It is local to the for loop. The for loop sets slot to each of the values between 1 and 16. Inside this for loop, line 11 gets the table value of the inventory item at slot and stores the value in item. Line 12 checks that item isn’t nil and also checks whether the item’s name matches the name parameter. If the names match, the program has found the item and selects that slot. Line 14 returns true to tell the code that selectItem() has selected the item.
The end statement on line 15 closes the if statement, and the end statement on line 16 closes the for loop statement. If the execution completes all iterations of the for loop and hasn’t found an item with a matching name, the function returns false on line 18. Finally, the end statement on line 19 closes the function statement.
The selectEmptySlot() function tries to find an empty inventory slot. If it finds one, it selects the slot using turtle.select() and returns true. If the turtle’s inventory is completely full, the function will return false. Like the selectItem() function, selectEmptySlot() has a for loop that checks inventory slots 1 to 16. The for loop in this function also declares the variable slot on line 28, which it can do because the slot variable defined on line 10 and this slot variable are in different local scopes.
hare
25. function selectEmptySlot()
26.
27. -- loop through all slots
28. for slot = 1, 16 do
29. if turtle.getItemCount(slot) == 0 then
30. turtle.select(slot)
31. return true
32. end
33. end
34. return false -- couldn't find empty space
35. end
Inside the loop, line 29 calls turtle.getItemCount() to check whether the number of items in the slot currently being checked is 0. When line 29 finds the first empty inventory slot, line 30 selects this slot and line 31 returns true.
If the execution makes it through all iterations of the for loop without finding any empty inventory slots, line 34 returns false.
In this chapter, you learned how to create modules and your own functions with function statements. Functions can have parameters, which are passed arguments as input. Functions can also return a value to the code that called the function.
Functions create a new local scope every time they’re called. When the function returns, the scope is destroyed along with all the variables in it, which means different variables can have the same name as long as they’re in different scopes. A for loop’s variable also exists in its own local scope inside the loop’s block. The functions in your hare module call other ComputerCraft functions: turtle.select(), turtle.getItemDetail(), and turtle.getItemCount().
You can place the functions you make inside modules to let other programs use them. Creating your own functions and modules is a programming technique that enables you to make more sophisticated turtle programs. In the Chapter 8, you’ll use the hare module to create a tree farm to automatically harvest wood logs.