Placing blocks can be time consuming and dangerous, especially if you fall from a great height when you’re creating large buildings in survival mode. Instead of building walls yourself, you can program a turtle to take on these hazardous and dull building jobs. In this chapter, you’ll design an algorithm to create walls of any size by adding more functions to the hare module. Although we’ll focus on creating stone brick walls in this chapter, the functions will work with any type of block, including dirt, glass, or even watermelons, as you can see in Figure 11-1.
Figure 11-1: Turtles building walls of dirt, stone bricks, glass, and watermelons
Let’s begin by adding two functions to the hare module: one that counts the number of blocks in a turtle’s inventory and one that builds walls.
We’ll start by creating three new functions that will count the items in the inventory, select and place items, and create walls. We’ll use these functions in other programs in this book, so we’ll put them into the hare module that we started in Chapter 7. The first 35 lines of hare will remain unchanged. Therefore, I’ll only show the code for lines 36 through 107.
From the command shell, run edit hare. Move the cursor to the bottom of the file and continue the code by entering the following:
hare
...snip...
36.
37.
38. -- countInventory() returns the total
39. -- number of items in the inventory
40. function countInventory()
41. local total = 0
42.
43. for slot = 1, 16 do
44. total = total + turtle.getItemCount(slot)
45. end
46. return total
47. end
48.
49.
50. -- selectAndPlaceDown() selects a nonempty slot
51. -- and places a block from it under the turtle
52. function selectAndPlaceDown()
53.
54. for slot = 1, 16 do
55. if turtle.getItemCount(slot) > 0 then
56. turtle.select(slot)
57. turtle.placeDown()
58. return
59. end
60. end
61. end
62.
63.
64. -- buildWall() creates a wall stretching
65. -- in front of the turtle
66. function buildWall(length, height)
67. if hare.countInventory() < length * height then
68. return false -- not enough blocks
69. end
70.
71. turtle.up()
72.
73. local movingForward = true
74.
75. for currentHeight = 1, height do
76. for currentLength = 1, length do
77. selectAndPlaceDown() -- place the block
78. if movingForward and currentLength ~= length then
79. turtle.forward()
80. elseif not movingForward and currentLength ~= length then
81. turtle.back()
82. end
83. end
84. if currentHeight ~= height then
85. turtle.up()
86. end
87. movingForward = not movingForward
88. end
89.
90. -- done building wall; move to end position
91. if movingForward then
92. -- turtle is near the start position
93. for currentLength = 1, length do
94. turtle.forward()
95. end
96. else
97. -- turtle is near the end position
98. turtle.forward()
99. end
100.
101. -- move down to the ground
102. for currentHeight = 1, height do
103. turtle.down()
104. end
105.
106. return true
107. end
After you’ve entered all of these instructions, save the program and exit the editor. You can also download this module by running pastebin get wwzvaKuW hare.
Before the turtle begins building, we need the turtle to check whether it has enough blocks in its inventory. Assuming that every item in the turtle’s inventory is a block that will be used in the wall, countInventory() returns the total number of items in all the turtle’s inventory slots.
hare
38. -- countInventory() returns the total
39. -- number of items in the inventory
40. function countInventory()
41. local total = 0
We’ll store the total count of the blocks in the total variable, which we initially set to 0. Then we’ll declare a slot variable that will keep track of which slot is being checked in the inventory.
Next, a for loop on line 43 iterates over all 16 inventory slots using the slot variable.
hare
43. for slot = 1, 16 do
44. total = total + turtle.getItemCount(slot)
45. end
46. return total
47. end
For each slot, the program calls the turtle.getItemCount() function and passes it the slot variable. The count of the blocks in each inventory slot is added to total. After the loop ends on line 46, the function returns total, which now contains a count of all the blocks in the turtle’s inventory. Note that the turtle.getItemCount() function counts all items, even if they’re not stone blocks.
To begin building, we need the turtle to place blocks from a nonempty slot. To accomplish this, we’ll add a function named selectAndPlaceDown() to the hare module. This function selects the first available nonempty slot and then places a block from that slot below the turtle.
hare
50. -- selectAndPlaceDown() selects a nonempty slot
51. -- and places a block from it under the turtle
52. function selectAndPlaceDown()
53.
54. for slot = 1, 16 do
55. if turtle.getItemCount(slot) > 0 then
56. turtle.select(slot)
57. turtle.placeDown()
58. return
59. end
60. end
61. end
Line 54 is a for loop that sets the slot variable to 1 on the first iteration, 2 on the second iteration, and so on up to 16. This is how the function selectAndPlaceDown() will scan all the slots of the turtle’s inventory.
Inside this loop, line 55 checks how many items are in the slot by calling turtle.getItemCount(slot). If more than zero blocks are in the slot, turtle.select(slot) on line 56 selects that slot and turtle.placeDown() on line 57 places the block below the turtle. Once the block has been placed and the function’s job is done, line 58 returns out of the function selectAndPlaceDown().
This selectAndPlaceDown() function works no matter what kind of building blocks are in the turtle’s inventory or which slots they’re in. When the turtle has nothing in its inventory, the loop ends and the function does nothing. The buildWall() and buildRoom() functions will call selectAndPlaceDown() as part of their code.
Next, we need to create the buildWall() function, which takes two parameters to control the size of the wall: one for the length and one for the height. Before writing the function, let’s design the algorithm the turtle will follow to build a wall.
When a turtle builds a wall, it needs to start on the ground, move up, and then place a block underneath itself. Then it needs to move forward and continue placing blocks until it has a line of blocks equal to length. Once the turtle has a line of blocks, it repeats the process in the opposite direction. The turtle continues moving back and forth, placing lines of blocks until it reaches the specified height. For example, let’s say that length is 4 and height is 2. Figure 11-2 shows a side view of where the turtle starts and what the 4 × 2 wall will look like when it’s built.
Figure 11-2: A side view of the turtle and the future 4 × 2 wall
The turtle needs to first move up and place a block below itself, as shown in Figure 11-3.
Figure 11-3: The turtle moves up and places a block below itself.
Because the turtle is building a wall four blocks long, the turtle moves forward three spaces, placing a block under itself after each move. If the turtle was building a wall that was six blocks long, it would move forward five spaces. Notice the pattern: the number of spaces the turtle moves forward is length - 1. To make the turtle place length blocks, we must make it move forward for each block except the last one, resulting in length - 1 forward moves. The buildWall() function can make walls of any length this way.
When the turtle has placed the first line of blocks for our 4 × 2 wall, the result will look like Figure 11-4.
Figure 11-4: The turtle moves forward, placing blocks below itself.
The turtle repeats this process, except this time it moves backward, as shown in Figure 11-5.
Figure 11-5: The turtle moves up and then moves backward as it places blocks beneath itself.
If the turtle had more rows to build, it would repeat the process, but by moving forward instead of backward. But because the turtle completed the number of rows needed for this example, the turtle is done building. In Chapter 12, we’ll want the turtle to make four walls to build a room, so the algorithm should always have the turtle finish on the ground at the opposite end of the wall from where it started. That way, the turtle will be in position to build the next wall.
To have the turtle move to the opposite end of the wall from where it started, there are two potential paths the turtle might need to travel based on which direction it was last moving. If the turtle was last moving forward, it would move forward once and then move down height number of times. If the turtle was last moving backward, it needs to move forward length number of times and then move down height number of times. Either path will always make the turtle finish at the far end of the wall, as shown in Figure 11-6.
Figure 11-6: The turtle ends up on the ground at the far end of the wall.
The buildWall() function can construct walls of any length and height. For example, as long as you have enough blocks, you could make a tall wall like the one shown in Figure 11-7. It’s all the same to the turtle.
Figure 11-7: A tall 4 × 12 wall is built using the same algorithm as the 4 × 2 wall.
Let’s examine the function that performs this algorithm.
The buildWall() function starts by counting the number of items in the turtle’s inventory. It assumes these items are all blocks that will be used to build the wall. The number of blocks needed to build a wall length blocks long and height blocks tall is length * height, which is what we check for on line 67.
hare
64. -- buildWall() creates a wall stretching
65. -- in front of the turtle
66. function buildWall(length, height)
67. if hare.countInventory() < length * height then
68. return false -- not enough blocks
69. end
If the turtle doesn’t have enough blocks to build the wall, the function returns false on line 68, which ends the function.
If the turtle does have enough blocks, the code on line 71 executes and the turtle moves up one block to start a new row. Then the program continues and sets the movingForward variable to true.
hare
71. turtle.up()
72.
73. local movingForward = true
The movingForward variable keeps track of which direction the turtle should go. For the first row, the turtle will move forward, so the movingForward variable should be true. But for the next row, the turtle needs to move backward, so we’ll need to change the variable to false later in the program. The variable’s value will alternate at each row as the turtle alternates the direction it moves.
To keep track of how high the turtle has built the wall, the program needs another variable, which we’ll call currentHeight. Line 75 starts a for loop in which currentHeight iterates from 1 to height. On the first iteration currentHeight is set to 1, on the next iteration it is set to 2, and so on, until the last iteration, where currentHeight is equal to height.
hare
75. for currentHeight = 1, height do
76. for currentLength = 1, length do
77. selectAndPlaceDown() -- place the block
78. if movingForward and currentLength ~= length then
79. turtle.forward()
80. elseif not movingForward and currentLength ~= length then
81. turtle.back()
82. end
83. end
As the turtle is building up, we also need to keep track of the length of wall it is building for each height level. To do that, we will use another variable called currentLength, which is created in a second for loop on line 76. This second for loop is nested in the loop on line 75. On each iteration of the first for loop, the program runs the nested for loop. This inner nested for loop iterates currentLength from 1 to length. Figure 11-8 shows what values currentHeight and currentLength are set to at each point of building a 4 × 4 wall. The red arrow shows the path the turtle takes.
Figure 11-8: The values of currentHeight and currentLength at each point of building a 4 × 4 wall
Inside the loop, line 77 calls selectAndPlaceDown() to place a block below the turtle. The code on lines 78 to 82 moves the turtle either forward or backward depending on the value in movingForward and whether the turtle is at the edge of the wall. The currentLength variable counts how many blocks across the wall the turtle has already traveled. When it is equal to the length of the wall, the turtle needs to stop so it doesn’t go past the edge.
Remember that the turtle doesn’t need to move on the last iteration. Because the for loop on line 76 iterates from 1 up to length, currentLength is equal to length on the last iteration. This is why the conditions on lines 78 and 80 run only if currentLength doesn’t equal (~=) length.
The end statement on line 82 ends the code block for the for loop on line 76. After this for loop, the turtle has placed blocks for the entire row. Now the turtle has to move up one space unless it is at the last iteration of the for loop on line 75. The turtle.up() call on line 85 runs only if currentHeight doesn’t equal height (that is, if the for loop isn’t at the last iteration).
hare
84. if currentHeight ~= height then
85. turtle.up()
86. end
87. movingForward = not movingForward
88. end
Line 87 toggles, or sets to the opposite value, the Boolean value in movingForward. If movingForward is true, the line sets movingForward to false, and if movingForward is false, the line sets movingForward to true. If the turtle was moving forward to build the highest row of blocks, the toggle will set movingForward to false. If the turtle was moving backward to build the highest row of blocks, the toggle on line 87 will set movingForward to true. Line 87 sets movingForward to its opposite Boolean value by simply setting it to not movingForward.
The end statement on line 88 ends the for loop that started on line 75. Once the execution gets past this loop, the entire wall has been built. Now the turtle needs to move back to the ground next to the wall. First, it needs to move off the wall and then travel down to the ground.
Recall that line 87 toggled the value in movingForward to make the turtle change directions. So if movingForward is now set to true, the turtle is closest to the starting point and needs to move length blocks forward to get off the wall, which it does on lines 93 and 94.
hare
90. -- done building wall, move to end position
91. if movingForward then
92. -- turtle is near the start position
93. for currentLength = 1, length do
94. turtle.forward()
95. end
But if movingForward is now set to false, the turtle is already at the opposite end of the wall and only needs to move one space forward, which it does on line 98.
hare
96. else
97. -- turtle is near the end position
98. turtle.forward()
99. end
Whether the turtle was moving forward or backward last, it will always need to move height blocks down to reach the ground level it started from, so the for loop on line 102 calls turtle.down() a number of times equal to height.
hare
101. -- move down to the ground
102. for currentHeight = 1, height do
103. turtle.down()
104. end
105.
106. return true
107. end
At this point, the wall has been built and the turtle is in the final position. Therefore, the buildWall() function returns true on line 106 to indicate the wall has been successfully built. The end statement on line 107 ends the buildWall() function’s code block.
The buildWall() function we put into the hare module is useful because building walls is a task that you might want to do in many programs. But if you just want to build a wall from the command shell instead of starting the Lua shell and typing os.loadAPI('hare') and hare.buildWall(4, 2), it would be easier to create and run a program to call this function for you. Let’s create the buildwall program to do this.
From the command shell, run edit buildwall and enter the following code:
buildwall
1. --[[Wall Building program by Al Sweigart
2. Builds a wall.]]
3.
4. os.loadAPI('hare')
5.
6. -- handle command line arguments
7. local cliArgs = {...}
8. local length = tonumber(cliArgs[1])
9. local height = tonumber(cliArgs[2])
10.
11. if length == nil or height == nil or cliArgs[1] == '?' then
12. print('Usage: buildwall <length> <height>')
13. return
14. end
15.
16. print('Building...')
17. if hare.buildWall(length, height) == false then
18. error('Not enough blocks.')
19. end
20. print('Done.')
After you’ve entered all of these instructions, save the program and exit the editor.
After placing the turtle and putting stone brick blocks (or any other kind of building block) into its inventory, right-click the turtle to open its GUI. From the command shell, enter buildwall 4 2 and press ENTER to watch the turtle build a wall four blocks long and two blocks high.
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 buildwall and then download it by running pastebin get 1aZ8BhNX buildwall.
The first part of the buildwall program loads the hare module and calls hare.buildWall() for you.
buildwall
1. --[[Wall Building program by Al Sweigart
2. Builds a wall.]]
3.
4. os.loadAPI('hare')
Although most of the work is done by the code in the hare module, the buildwall program has some additional features. When calling hare.buildWall(), the program needs to know what values to pass for the length and height parameters. The buildwall program can get these values from the command line arguments that the player enters when running the buildwall program from the command shell. Earlier, you passed the buildwall program the command line arguments 4 and 2. You’ve already used command line arguments when running other programs, such as in Chapter 2 when you passed the label program the command line arguments set Sofonisba in label set Sofonisba. Command line arguments are stored as a type of table value called an array in a Lua object named {...}, which is made up of two braces with three periods in between. Before looking at the code that takes the command line arguments, let’s look at how arrays work.
The table values that you first learned about in Chapter 7 can store multiple values. Arrays are another data type whose values can store multiple values. The values in a table are stored with key-value pairs, whereas the values in arrays are stored in number order. To access a value in an array, you would use its numerical position, which is called an index.
NOTE
Technically, arrays in Lua are just another type of table. The tables with key-value pairs we’ve used in previous chapters are called map-like tables, whereas arrays are called array-like tables.
The code for arrays is similar to the code for tables. To create an array, use braces, {}, just as you would for tables, but omit the keys. You don’t need to enter keys because the position of the values tells Lua what the value’s index is. Enter the following into the interactive shell to create your own array:
lua> pets = {'mouse', 'cat', 'dog'}
To access the values in an array, enter the name of the array, and then put the value’s numeric index in between square brackets, []:
lua> pets[1]
mouse
lua> pets[2]
cat
lua> 'I have a pet ' .. pets[3]
I have a pet dog
Array numbering starts at 1 in the Lua programming language, so to access the first value in the pets array, you would enter pets[1].
Returning to the buildwall program, line 7 stores the command line argument’s array-like table value in a variable named cliArgs. This variable lets your code access the individual values inside the table value using square brackets: cliArgs[1] is the first command line argument, cliArgs[2] is the second, and so on.
buildwall
6. -- handle command line arguments
7. local cliArgs = {...}
8. local length = tonumber(cliArgs[1])
9. local height = tonumber(cliArgs[2])
All command line argument values are stored as strings, even if they seem like numbers, such as '4' or '2'. To convert values from strings to number values, lines 8 and 9 pass the command line arguments to the tonumber() function, which returns number values of the strings passed to it. We’ll store these number values in the length and height variables.
If the player doesn’t enter any command line arguments (or enters only one command line argument instead of two), the program won’t have enough information to run. Maybe the player doesn’t know they were supposed to pass numbers for the length and height, or maybe they forgot to type them. In those cases, it’s helpful for programs to display a usage message, which reminds the player how to run the program. The usage message also appears if the player uses ? as the first command line argument.
buildwall
11. if length == nil or height == nil or cliArgs[1] == '?' then
12. print('Usage: buildwall <length> <height>')
13. return
14. end
If there are no command line arguments or the arguments are not numbers, cliArgs[1] and cliArgs[2] will be set to the nil value. Passing nil or a string that doesn’t contain a number to the tonumber() function will result in it returning nil. For example, if the player doesn’t enter any command line arguments, length will be set to nil on line 8. If the player passes only one argument, height will be set to nil on line 9. The condition on line 11 checks if length or height are nil or if the first command line argument is the '?' string.
In situations where the player doesn’t enter valid command line arguments, the print() call displays a usage message that tells the player to type buildwall followed by the command line arguments for the length and height. The angle brackets, <>, around length and height indicate that the player shouldn’t type the words “length” and “height” but should instead type numbers for the length and height command line arguments.
All of the hard work for the buildwall program is in the hare module’s buildWall(), selectAndPlaceDown(), and countInventory() functions. So the buildwall program’s code is simple. After preparing the command line arguments, it just calls the hare.buildWall() function on line 16 and passes in the values for length and height.
buildwall
16. print('Building...')
17. if hare.buildWall(length, height) == false then
18. error('Not enough blocks.')
19. end
20. print('Done.')
Line 16 calls print() so the user can see that the program has started. When the hare.buildWall() function returns false, there are not enough blocks and the program won’t run. We check for this on line 17 and show the user an error when that is the case. Then, line 20 prints a message to tell the user the program has finished, whether it has built the wall or given the user an error message.
That’s the entire buildwall program! The code in the hare module handles the rest of the instructions.
In this chapter, you expanded your hare module with new functions. The countInventory() function gives you the total number of items in the turtle’s inventory. This is a useful way to check whether the turtle has enough blocks to build with. The selectAndPlaceDown() function makes the turtle select a particular block in its inventory and place it in the Minecraft world. With these functions, you can write code to construct walls of any size!
You also learned how to write programs that can read command line arguments. You’ve been using command line arguments since Chapter 2 (such as when you used set Sofonisba in label set Sofonisba). But in this chapter, you learned that you can access the table value of the {...} object to get the command line arguments the player enters when they run the program.
When other people run your programs, they probably won’t know how to read Lua code to figure out what command line arguments are required for your program. Therefore, programs can display a usage message that gives a brief description of the command line arguments.
In Chapter 12, you’ll build on the wall-building program (I never apologize for my puns) to create rooms with four walls.