Note
This article is still a work in progress.
Open Minecraft, go into a singleplayer creative world, then place down an "Advanced Mining Turtle". A turtle is essentially a robot that can move around, break and place blocks, and a couple of other things depending on what tools you give it.
Then right click on the turtle to open its UI.
Welcome to the shell, also known as the command prompt. It looks way scarier than it is.
The only thing you can do from the shell is run programs by typing the program's name and hitting enter.
Computers come with a couple dozen pre-written programs, but soon we'll be writing our own and the shell is also where we'll be running them from.
For now the only program we care about is lua so let's run that:
Welcome to the lua repl. This is a place where we can directly write and immediately run lua code. It's useful for experimentation as we can run individual lines of code one by one to see what they do, and later once we've figured out what we want the turtle to be doing we can write an entire program.
In Lua every capability of the computer is represented as a function. Functions have names and can be called in order to do their corresponding action. The words "calling", "executing" and "running" are all essentially synonyms.
The syntax for calling a function in lua is the following: functionName(argument1, argument2, argument3, etc...)
In the above functionName stands for the name of the function we'll be calling. After that follows an opening parenthesis, and then any number of arguments.
Arguments (also known as parameters) are additional information we can give to the function with which we can specify exactly what it should be doing.
A lot of functions don't accept any arguments, in which case the syntax would just be functionName().
Fearing that the above might be too much text, let's start writing some code.
What turtle.dig() does should be pretty clear from the name of the function:
Specifically this mines the block in front of the turtle, there's also turtle.digDown() and turtle.digUp() for the other two directions:
Similarly we have turtle.place(), turtle.placeDown() and turtle.placeUp() to place blocks.
The block placed is whatever block the turtle currently has selected from its inventory.
Changing what item the turtle currently has selected is done with turtle.select, and this is our first example of a function that takes an argument, that being the number of the slot we want to select:
Let's now try moving the turtle. Moving forward is done via turtle.forward():
Uhhh... Right. This failed and it tells us why: turtles need fuel to move. Let's put some fuel in the turtle's currently selected slot.
And after that we can call turtle.refuel()
Now we should be able to move forward with turtle.forward():
There's also turtle.back(), turtle.up() and turtle.down() to move the turtle in other directions. To turn the turtle we can use turtle.turnLeft() and turtle.turnRight():
Typing turtle.dig() into a terminal to mine a block is no easier than just mining it yourself.
In general the power of computers comes not from doing things once but doing them over and over again on repeat.
This is what's usually called automation.
The first construct I will show you is just an infinite loop.
The code while true do ...... end will run whatever program we write in place of the dots over and over forever.
Let's automate digging blocks via while true do turtle.dig() end:
And with that I'd like to congratulate you on making a fully working cobblestone generator.
With infinite loops comes the danger of getting stuck. You can always end the current program's execution by either holding Ctrl+T or by pressing the terminate button ⦸ on the top left:
Time for something more complicated. Instead of just placing one function call in the loop we can place multiple and they'll each run in sequence.
Another simple program we can make with that is while true do turtle.forward() turtle.placeDown() end. This is a program for creating a bridge! Very useful for exploring the nether.
There's a slight problem though, when it runs out of blocks it keeps going...
Ideally after it runs out of blocks we'd like for it to stop. This is done by making our infinite loop finite.
Instead of doing while true do ...... end we can do while someCondition do ...... end, where someCondition stands for any expression that can be true or false.
Before each iteration of the loop the condition will be computed and the loop will continue only if it is true, if it is false the loop will immediately end.
Going back to the lua repl, we want the loop to end when we run out of blocks, so let's try to write some lua code that checks if the turtle has blocks.
After reading the documentation we find the function turtle.getItemCount(), let's see what it does:
Promising. Now, what we need is a true/false statement and not a number, but we can easily fix that with some math:
We have our condition, and slotting it into our previous program we get while turtle.getItemCount() > 0 do turtle.forward() turtle.placeDown() end.
Almost reads like english, "while you have items go forward and place them down".
The last program we just wrote is very useful, but it's kind of starting to get long. Instead of typing it all out every time we want to bridge in the nether it'd be way more convenient to have it saved in some way. Thankfully we can do exactly that by writing it down in a file.
First, quit the lua repl to go back to the shell by running exit()
From here we'll now open a text editor in which we can write our program. Type edit bridge.lua and press enter:
Welcome to the built-in text editor. It's very barebones but it'll do the trick. Write down your program.
Now, so far we've been writing our programs on a single line, but even a relatively simple program like this one is getting unwieldy to manage when it's written out on a single line. Let's split it up into multiple by hitting enter in a couple of places:
Now the whole thing fits neatly on the screen. Another nice thing we can do is indent the lines that are inside the loop by two spaces:
Our program is now functional and beautiful. Let's save it. The built-in editor is a bit weird when it comes to that. You have to first press control on your keyboard, then that opens a menu which you can navigate with left and right arrow keys, and you then have to press enter to do the selected thing. So first press control on your keyboard to open the menu...
...then press enter to save the file...
...then press control to open the menu again...
...then right arrow twice (or once on a non-advanced turtle) to navigate to "exit"...
...then press enter to exit the editor.
You can check that you saved it correctly by running the ls program (short for "list", it lists all of the files we have).
This is what you should be seeing:
Time to run our program. The name of the file is the name of our program, so give the turtle some blocks and then run bridge:
Create a program that turns a stack of concrete powder into a stack of concrete. Similarly to the cobblestone generator you'll need to construct a setup around the turtle for this to be possible.
After you've made something that works read the solution below.
Solution
One solution to the problem is the following setup and code:
while true do
turtle.place()
turtle.dig()
endThis definitely works and would earn you full points. Another solution is the following setup with two turtles and two programs:
while true do
turtle.place()
endwhile true do
turtle.dig()
endWhile this requires 2 turtles it is correspondingly 2x faster while only requiring one of the two turtles to have a pickaxe. This solution would again earn you full points.
Congratulations, you're a wizard now. What we've done together is test things out in the lua repl, written a complete program, saved it to a file, then ran it and it (hopefully) worked. Throughout the rest of this tutorial this is what we'll keep doing over and over, with the only difference each time being that our programs will get increasingly more sophisticated in order to solve increasingly more complicated problems. There is much more for you to learn, but in the business of wizardry the learning never stops. On that note, let's continue the learning.
Sometimes when you leave a turtle to do something and then come back you can catch it slacking on the job:
That is because when a computercraft computer of any kind gets rebooted it completely forgets what it was doing.
When you go away the chunks with the computer get unloaded and the computer shuts down, then when you come back the computer gets booted up again but no longer knows what it is supposed to be doing.
The solution to this is writing a file called startup.lua. This name is special because when a computer boots up it looks for a program called like that and if there is one it runs it.
So in our case if we put our cobblestone mining program in a file called startup.lua the turtle will resume mining cobblestone even after rebooting.
This is another opportunity to practice writing code into files with edit:
Now if you did it correctly when you reboot the turtle manually it should start digging. You can do that either via the red power button ⏻ at the top left or by typing reboot:
We talked about while loops, let's now talk about if statements. If while loops are a way to execute code multiple times, if statements are a way to not execute it at all. The basic structure is like this:
if someCondition then
...ifTrue...
else
...ifFalse...
endAgain we have some kind of condition (aka. something that's either true or false), but this time there's two blocks of code inside. If the condition is true the first block is ran and the second one is ignored, if the condition is false the first block is ignored and the second one is ran.
On their own if statements don't have much use, it's only when we combine them with other things like while loops that they becomes useful. Let's do that.
Lamps activated by inverted daylight detectors are a classic.
But with this simple setup we don't have control over what time exactly the light is on. If we instead have a computer control the light we can tell it exactly when it should turn on and off, and we could even have complicated schemes like turning it on and off multiple times in a single day. Let's do that!
I've made the following build where a regular computer is hidden under trapdoors. You can probably build something prettier than this but for this example it will do:
There's two things we don't know yet, and that's how to tell the time and how to turn the lamp on and off. Looking through the documentation we can find the redstone api, and from it redstone.setOutput seems to be what we want. As for the current time there's os.time() which returns the current hour of the day in a 24-hour format (0 is midnight, 12 is noon, 13 is 1pm, 19 is 7pm, etc.). With those two things out of the way we can start writing code.
I just told you about if statements so it's fair to say we'll probably need them. Let's just write one down.
if someCondition then
......
else
......
endWe want the lamp to be on when it's night and off when it's not, so let's write that down.
if isNight() then
turnOnLamp()
else
turnOffLamp()
endOf course these three functions don't exist, but we can start replacing them one by one with actual code. turnOnLamp and turnOffLamp can both be done with redstone.setOutput like so:
if isNight() then
redstone.setOutput("top", true)
else
redstone.setOutput("top", false)
end...I actually forgot to tell you what arguments redstone.setOutput takes. Here:
This is a screenshot of the relevant bit of documentation. The first line is the signature of the function. It tells us the name, then the number of arguments and gives a name to each argument. The first argument is called "side" and the second argument is called "on". Below that is a description of what the function does, and below the general description is a description of what each argument is supposed to be. The argument called "side" is supposed to be a string, which is programming jargon for "text" (and in lua we write text between "quotation marks", otherwise the text will be interpreted as code). The argument called "on" is supposed to be a boolean, which is programming jargon for "true or false". With that context it should now be pretty clear what each of the two lines I just wrote do.
Now time to replace isNight(). From messing around with /time set it seems the sun sets at 19 and rises at 5.5 (aka. 5:30).
This means if the time is over 19 or less than 5.5 it's night.
if os.time() > 19 or os.time() < 5.5 then
redstone.setOutput("top", true)
else
redstone.setOutput("top", false)
endWe're done... right? This sure is code at least, so let's run it.
...It immediately finishes. Yeah that makes sense, there's no loop in here. The code as-is only checks the time once, sets the lamp accordingly, then stops. What we want is for it to keep checking the time and keep setting the lamp, so what we have to do is take all of this and put it in an infinite loop:
while true do
if os.time() > 19 or os.time() < 5.5 then
redstone.setOutput("top", true)
else
redstone.setOutput("top", false)
end
endTesting this program it seems to work!... For about 10 seconds. Then it errors.
I won't go into detail about what "yielding" is, but the cause of this error is that our loop has nowhere for the computer to "take a break" per se. We've told it to keep checking the time and updating the lamp as fast and as often as it can, and that's usually not a good thing to have on a multiplayer server where the computer is consuming the server's resources to do these checks. The sleep() function comes to the rescue. It accepts as an argument some number of seconds and all it does is tell the computer to do nothing for that amount of seconds. Let's insert a sleep at the end of our loop such that the computer will only check the current time once per second.:
while true do
if os.time() > 19 or os.time() < 5.5 then
redstone.setOutput("top", true)
else
redstone.setOutput("top", false)
end
sleep(1)
endFinally, some working code.
For your purposes you can adjust the two numbers as you see fit. If you modify the condition of the if statement you can even have it turn on and off multiple times per day, for example like so:
while true do
if (os.time() > 19 and os.time() < 21) or (os.time() > 5.5 and os.time() < 7.5) then
redstone.setOutput("top", true)
else
redstone.setOutput("top", false)
end
sleep(1)
endThis lamp will be on from 7pm to 9pm as well as from 5:30am to 7:30am. Could be useful for automating the lights in your house, perhaps?
If you do end up using this in your world make sure to name the program file startup.lua so that it keeps working.
A -- followed by any text is a comment. Comments are parts of the code that are completely ignored by the computer and therefore do absolutely nothing. The primary use for comments is to explain the rest of the code as you can write text freely in them. Here's the previous program with some comments added:
while true do
-- check if it's dark outside but not bed time
if (os.time() > 19 and os.time() < 21) or (os.time() > 5.5 and os.time() < 7.5) then
redstone.setOutput("top", true) -- turn on lamp
else
redstone.setOutput("top", false) -- turn off lamp
end
sleep(1) -- fix "too long without yielding" error
endHow you use comments is completely up to you. A perfect program wouldn't need any comments because it would be completely self-explanatory, but I don't write perfect programs so I find comments very useful for explaining non-obvious parts of my code. You will be seeing me use comments a lot from now on.
Comments are useful not only for explaining your code to other people but also for explaining your code to future you. After writing a program it can take as little as a week for you to completely forget why you wrote something the way you did, so comments exist as a message in a bottle for your future self to be able to pick up from where you left off.
Let's revisit our bridge building program. Here is the code we have so far:
while turtle.getItemCount() > 0 do
turtle.forward()
turtle.placeDown()
endIf you tried to use this program in-game you might notice that the program outright ending when the turtle runs out of blocks is kind of inconvenient. Ideally it would wait for more blocks then start building again. We just learned waiting can be done with sleep() so it should definitely be possible to make it happen and we also should already know everything needed to make it happen.
I encourage you to try solving this on your own as an exercise, but I want to use this problem to showcase two different... tools for thought, I could say. Programming is, in some sense, the process of describing how you think. If you were in the computer's place, what would you consider before making your decisions, and what would those decisions be? This act of self-reflection is hard to teach, and usually taught by showing rather than telling. I've secretly been doing that this entire time by showing you several examples of how I go from nothing to a fully working program. But apart from the showing, I do have something to tell; Two "cheat codes", per se, which greatly aid the process of going from an idea to a program.
Flowcharts are an incredibly intuitive way to describe step-by-step instructions. The way they work is pretty self-evident as the following xkcd comic demonstrates:
This means that someone who isn't well-versed in structuring their thought as programs can probably structure their thought as a flowchart way more easily.
What we want our bridge building program to do is "bridge if it has blocks, wait if it doesn't, do that forever", and that should be easy enough to translate into a flowchart form:
To go closer to lua we first replace all of the english actions with appropriate function calls:
Then it's kind of clear that the diamond shape is supposed to be an if statement, and because this program never ends we're supposed to have an infinite while loop somewhere, but it's not yet clear where. We can rearrange the arrows to make it much clearer:
After this it should be pretty clear how the chart translates into code:
while true do
if turtle.getItemCount() > 0 then
turtle.forward()
turtle.placeDown()
else
sleep(1)
end
endFlowcharts are capable of representing any program. In an alternate universe flowcharts would probably be the de-facto way of programming, but sadly that's not this universe. You can draw flowcharts either on a computer using various drawing tools or with pen and paper. There's even software that can directly execute programs represented as flowcharts but I won't be going into that.
A technique so useful that I've already been using it this whole time and even used it in the flowchart example. The strategy is simple: start from an english description of what you want, then progressively translate that into code chunk by chunk by adding more and more details. In the flowchart example I started from "bridge if it has blocks, wait if it doesn't, do that forever" and then made a flowchart out of that, but we can instead make a pseudo-program out of it like so:
while true do
bridgeIfYouHaveBlocks()
waitIfYouDontHaveBlocks()
endAssuming bridgeIfYouHaveBlocks() and waitIfYouDontHaveBlocks() do what their names say the program will do what we want.
The two functions don't actually exist, but we can replace them with code that does. I'll do the first one first:
while true do
if turtle.getItemCount() > 0 then
turtle.forward()
turtle.placeDown()
else
end
waitIfYouDontHaveBlocks()
endAnd now the second one:
while true do
if turtle.getItemCount() > 0 then
turtle.forward()
turtle.placeDown()
else
end
if turtle.getItemCount() > 0 then
else
sleep(1)
end
endAnd now you may notice we can merge the two if statements into one
while true do
if turtle.getItemCount() > 0 then
turtle.forward()
turtle.placeDown()
else
sleep(1)
end
endTa-da! Same final result.
The resulting program we get heavily depends on what choices we make along the way. Here our choice of an initial description about what we want matters. If we instead described what we want as "bridge while we have blocks, wait while we don't have blocks, repeat" we'd get this:
while true do
while turtle.getItemCount() > 0 do
turtle.forward()
turtle.placeDown()
end
while not (turtle.getItemCount() > 0) do
sleep(1)
end
endWhich does the exact same thing but is noticeably different. (There aren't even any if statements in this one!)
A third program we might've ended up with is this:
while true do
while turtle.getItemCount() > 0 do
turtle.forward()
turtle.placeDown()
end
sleep(1)
endWhich again does the exact same thing but is different from the previous two.
Neither of these three programs is inherently better, but you probably have some preference for one over another. This is perfectly normal and is a reflection of the way you think about the task at hand.
Create a program that automatically chops down and replants a tree over and over in order to automate wood production. Specifically your program should:
- Wait for the sapling to grow up into a tree
- Chop up the tree block by block
- Go back down to its original position
- Plant another sapling
- Repeat steps 1-4 forever
The turtle's inventory should contain saplings in the first slot for replanting while keeping the collected wood in the other slots.
You'll need a couple more methods to do this. turtle.detect(), turtle.detectUp() and turtle.detectDown() return true if there's any block on that side of the turtle and false otherwise. turtle.compare(), turtle.compareUp() and turtle.compareDown() return true if the block on that side of the turtle is the same as the block the turtle is holding. (This is what you should use to determine if the sapling has grown up into a tree.)
After you've made something that works read the solution below.
Solution
We can pretty directly turn the description of the problem into pseudocode:
while true do
waitForSaplingToGrowUp()
chopUpTree()
goBack()
plantSapling()
endAnd then we can replace each fake function with code one by one.
turtle.compare() will return true if the sapling has not grown up yet and will return false when it has grown up, so we just have to wait while it keeps returning true. Well, the code for that is:
-- waitForSaplingToGrowUp()
while turtle.compare() do
sleep(1)
endNext up, chopping up the tree. The first block is kind of special because it's in front of the turtle instead of above it so we have to write some code just for it:
turtle.dig()
turtle.forward()
......But for the rest we just dig up and go up while there's still blocks above us to dig up.
......
while turtle.detectUp() do
turtle.digUp()
turtle.up()
endPutting it all together we get:
-- chopUpTree()
turtle.dig()
turtle.forward()
while turtle.detectUp() do
turtle.digUp()
turtle.up()
endNext is goBack(). We don't know how many blocks up we went in the previous step but we can just keep going down until we reach the ground:
while not turtle.detectDown() do
turtle.down()
end
......And then we just need to go back by 1 to reach our original position:
-- goBack()
while not turtle.detectDown() do
turtle.down()
end
turtle.back()Finally to replant the sapling we just do turtle.place()
-- plantSapling()
turtle.place()Assembling everything into a whole program we get:
while true do
-- waitForSaplingToGrowUp()
while turtle.compare() do
sleep(1)
end
-- chopUpTree()
turtle.dig()
turtle.forward()
while turtle.detectUp() do
turtle.digUp()
turtle.up()
end
-- goBack()
while not turtle.detectDown() do
turtle.down()
end
turtle.back()
-- plantSapling()
turtle.place()
endA solution similar to this one would earn you full points. (If your solution is not similar but still works then it will also earn you full points.)
As a footnote, there's actually an alternate way to implement goBack() that uses the fact turtle.down() returns true if it succeeded and false if it didn't:
-- goBack()
while turtle.down() do
end
turtle.back()In this version we use turtle.down() both as the condition and the action, and keep the body of the loop empty.
I hope that by now you've become somewhat comfortable writing code and expressing your thinking through code. We've gotten far with just if statements and while loops, and now I plan to introduce you to some fundamentals that will both unlock new possibilities and aid us greatly in the development process. But before all of that...
Yeaah I kinda skipped explaining what numbers are and what you can do with them, among other things. This is usually the very first thing any programming book would teach, but it's also the most boring part of all of those books, which is why I've been delaying it so much. It's a necessary thing to explain regardless so here we go.
Let's begin with the booleans, which if you remember is jargon for "true" and "false".
We can confirm that these values are in fact called "booleans" using the type() function:
Booleans are the values we've been using for the conditions for if statements and while loops. (More specifically we've been calling functions which return booleans.)
There's several operations we can do to booleans. Using the not keyword we can negate them:
And using and and or we can combine them:
These three keywords are what we've been using to make complicated conditions for our if statements and while loops.
That's basically everything about booleans. Now onto an even simpler value, nil:
Nil is the value for nothing. Literally nothing. It's similar to 0 in that regard but it's not even a number, it just represents the lack of any value. This is similar to null or undefined from other programming languages if you've used any.
The applications of nil are not immediately apparent but we'll slowly be seeing it more and more as more concepts get introduced.
Next up is strings, which is jargon for text.
Strings are pretty handy. In fact that's what type() has been returning this whole time
Strings can be manipulated using various functions in the string set of functions. You can glue strings together, cut off parts, or replace parts with other parts. You can read about those functions in chapter 6.4 of the lua manual since I don't plan on doing much string manipulation in this tutorial.
Next up, functions. You've seen them a thousand times by now but maybe you didn't know they're actually values.
This is why even when a function takes no arguments we need to type () afterwards, otherwise we're just referring to the function as a value instead of calling it.
Functions being values means we can pass functions as arguments to other functions, which so far we haven't really done (other than in the above screenshot) but will be very handy later on. It also means a function can return another function, which might also be handy later.
And now, numbers. There's one type of number in lua, which lua calls "number":
But specifically the kind of numbers lua has are called floats, short for "floating-point numbers". These numbers are capable of representing both whole numbers and fractional numbers, both really big numbers and really small numbers.
We can do basic math with numbers, with + being addition, - being subtraction, * being multiplication and / being division:
We can also do slightly less basic math. ^ is exponentiation:
And % is the "modulo" operator. a % b is the remainder of dividing a by b if it was done as whole number division:
And if that's still too basic for you there's the math set of functions that includes trig functions, exponentials and logarithms, square root, and so on. Basically all of the buttons from your scientific calculator are here.
Beyond that we have ways to compare numbers. We've already seen < and > but here they are again:
These operators test for strict inequality. For non-strict inequality (aka. ≤ and ≥) we have <= and >=:
Additionally there's the equality (aka. =) operator == and the inequality (aka. ≠) operator ~=:
The reason equality is tested with == rather than just = is because = is used for another operation that we'll get to later.
== and ~= can actually be used on everything else and not just numbers:
Math stops mathing really quickly when we're dealing with floating-point numbers:
The above expression is a rather famous example and often gathers a lot of "what! why! how!" before the trick is revealed:
Wait no, that can't be right. Holdup, we'll need to go to a different version of lua...
There, now the problem is obvious; An error of 0.00000000000000004 was introduced during the addition. This is because fractional numbers are not stored precisely and are instead stored only to some number of digits. It's similar to the way you might write 2/3 as 0.66666666666 and 1/3 as 0.33333333333, and then later when you add the two you get 0.99999999999 instead of 1.
You might protest that 0.1 only has 1 digit, but floats are stored in binary, and in binary the same number is written as 0.0001100110011001100110011001100110011.... So the same way almost every fraction has an infinite number of digits in decimal, almost every fraction has an infinite number of digits in binary as well.
Oh and if you're wondering why in minecraft the result appeared as 0.3 instead of 0.30000000000000004, that's because the in-game lua repl did some rounding for us while displaying the number.
This problem does not exist for whole numbers though, horray!
Or does it? Let's try bigger numbers:
Haha it does exist, and is in fact the exact same problem!
When numbers get too large they're stored in scientific notation, and again when we have too many digits we lose precision. So numbers with too many digits get approximated as well.
But what's the limit of that? How big can we go? Well...
At some point we reach infinity. There's a second way to reach infinity, and that's by dividing by zero:
You were probably told infinity is not a number. That's correct, it's not a number, but in lua it is:
You can do all sorts of fun things with infinity:
But this is all stuff you already knew about infinity. What about the age-old questions? What's infinity minus infinity? What's infinity times zero? According to lua the answer is...
"NaN". This is short for "not a number". Except that's false advertising because it's a number:
At least according to lua, anyway. Your math teacher has probably never heard of it.
NaN has the role of acting as a garbage value. Pretty much anything you do with NaN will again give NaN:
The reason NaN and infinity exist (alongside negative infinity) is because according to the IEEE 754 standard (that's the document that describes how floats should work) math operations on floating point numbers should never error. They should always succeed, even if the result can't be anything other than garbage. That same standard also defines exactly what that garbage should look like, hence these three standard kinds of garbage values.
Funnily enough the weirdness does not end here. You might remember from math class that every number is equal to itself. Well in lua every value is equal to itself, except for one...
NaN is the only value that is unequal to itself. All NaNs are special and no two NaNs are created equal.
That was kind of a lot, so it'll probably be helpful if I list all of the things you're supposed to take away from the above rant:
- Fractional numbers are stored inaccurately
- Whole numbers are stored accurately, unless they get large in which case they're again stored inaccurately
- Dividing by zero and other math poo-poos yield garbage instead of erroring in any way
- Use
==and~=only for accurate numbers. For inaccurate ones only use<and>
These considerations mostly only matter when you're trying to do complicated math, and even then they often don't. None of this is specific to lua, by the way. Almost all modern programming languages have floating point numbers that work this way.
So far our tool for creating and editing programs has been the edit editor. It's not bad but it's lacking many conveniences you'd expect from even the most basic editor (like copy-paste for example). I'll now show you a better tool made by the same person who's currently maintaining CC:Tweaked, and that's Cloud Catcher. It's a website that connects to your in-game computer and lets you control it from your browser. You don't need to install anything to use it (but you do need a computer with enough ram to have both minecraft and a web browser open, which might be a big ask).
First, go to https://cloud-catcher.squiddev.cc/ It should look something like this:
This page technically contains usage instructions but they're quite terse so I'll guide you through the setup process. The top of the page is the most important part, it's giving us two shell commands to run in-game:
wget https://cloud-catcher.squiddev.cc/cloud.lua
cloud.lua 7rDjbc9SNkjlOtgCcS1pqQpaW5mF0n6S
The first command is a wget command, short for "web-get". Its job will be to download the cloud.lua program onto our computer. We only need to do this the first time we're using cloud catcher.
The second command then runs this cloud.lua program we just downloaded and the argument is a session token. This corresponds to the session we have open in our web browser right now. After running the second command what we see in the web browser will change:
We're now connected. What we see is an exact mirror of the in-game computer's screen. Anything we type here will be reflected in-game and anything we type in-game will be reflected here.
This is not what I wanted to show you, though. Let's say we want to make a new program called spin.lua. We'd normally do that via edit spin.lua, but with cloud catcher we can instead run cloud edit spin.lua:
We are now editing the file in our browser. Let's write some code to see what it looks like.
Great, and the big dot on the left shows us the file isn't saved. We can press Ctrl+S to save it like in any other editor:
Time to test our program. Cloud Catcher uses a tabular design so we don't even have to stop editing the file to try out the program, we can just click on the main tab to switch back to the terminal and we can immediately run our program.
The program we wrote is infinite so to stop it we can use this button on the bottom right:
Now here's another cool part, we can edit multiple programs simulatenously by just running cloud edit again:
This mostly concludes my introduction to using Cloud Catcher. It requires slightly more setup than just using edit but the improved ergonomics very much make it worth it.
The VSCode extension CraftOS-PC offers a similar experience to Cloud Catcher but through the VSCode editor. This has the same tradeoff of more setup for more ergonomics since the VSCode editor is better than the one Cloud Catcher provides. It's an excellent extension that I recommend if you're a VSCode user. Since it's well-documented I trust that you'll figure out how to set it up on your own.
The function print() takes a string (or number) and displays it on the screen:
print("here's an example")
The weird and unhelpful name comes from the 1960s when computers were connected to teleprinters (basically electric typewriters) for their input and output, so back then the print() function would actually print something onto real paper. We haven't been using teleprinters for over 50 years now but the name remains.
print() has a couple of uses. One of them is pairing it with read() to do user input and output and thus have some kind of interactive interface. (As an example of such a thing try the adventure program on a normal computer.) Since this tutorial focuses mainly on getting turtles to do stuff in the world we'll be doing little of that. Instead what we'll be using print() for is helping us fix mistakes in our programs, which is also known as debugging.
The way print() helps us with debugging is pretty simple. We can put print() calls at various points in our program and then as the program runs we will see the order in which those calls happen.
To demonstrate I'll take one of the versions of our nether bridging program:
while true do
while turtle.getItemCount() > 0 do
turtle.forward()
turtle.placeDown()
end
sleep(1)
endAnd then put print() calls in all of the important places:
while true do
print("start of loop")
while turtle.getItemCount() > 0 do
print("marching on!")
turtle.forward()
turtle.placeDown()
end
print("sleeping")
sleep(1)
endThen when we run this program we can see exactly what the turtle is doing when it has no blocks:
And what it's doing when it does have blocks:
As well as what it's doing when it had blocks but ran out:
The results are exactly as one would expect since this program has no mistakes. If it did have mistakes then using the output of the print()s we'd be able to more easily find that mistake than if we were just guessing where it might be.
Remember all of those times I said "These functions don't actually exist, but we can replace them with code that does"? Turns out we did not have to do that, in Lua we have the ability to define our own functions so we could've just made those functions real.
The syntax for defining functions is as follows:
function functionName(argumentName1, argumentName2, argumentName3, ...etc...)
...code...
endfunctionName will be the name of our newly created function. Then, just like how built-in functions can take arguments, our functions are also capable of taking arguments. The way we do that is by giving a name to each of those arguments, and then in our code we can use the names we chose as synonyms for the values that are passed to us when our function gets called.
The above paragraph might be a bit too abstract so it's time for an example:
function praiseValue(value)
print("We hereby pronounce")
print(value)
print("as the value of the month!")
end
praiseValue(7)If we run this code we'll see:
Which is identical to what would happen if we wrote:
print("We hereby pronounce")
print(7)
print("as the value of the month!")One of the benefits of functions is that they're reusable. We praised one value so far but if we want to praise several values then doing it with a function is much easier and takes much less typing:
function praiseValue(value)
print("We hereby pronounce")
print(value)
print("as the value of the month!")
end
praiseValue(7)
praiseValue(16)
praiseValue(true)
praiseValue(93)
The other big benefit of functions is it makes pseudocode programming much easier as we can just use fake functions and then make them real afterwards.
Turtles are really useful for automating building. As a simple exercise to get us started let's automate building a + sign:
Our turtle is going to start in the middle of the +
It's then going to place the central block:
And then build each arm of the + sign one by one:
Let's start by writing our pseudocode based on the above description:
placeCentral()
buildArm()
buildArm()
buildArm()
buildArm()Great. Now instead of replacing each of these function calls with code we can just define the functions. placeCentral() could not be easier to write:
function placeCentral()
turtle.placeDown()
endBut buildArm() might be a bit more difficult. Let's examine that step in detail. This is the state of the build before buildArm() is called:
So what we want is for the turtle to go forward and place down a block 3 times:
But then we have to remember that by the end of this function call we need to be in position to build the other arms of the +, hence we need to also go back to the start:
Sounds like a plan, and it's easy to write as code:
function buildArm()
turtle.forward()
turtle.placeDown()
turtle.forward()
turtle.placeDown()
turtle.forward()
turtle.placeDown()
turtle.back()
turtle.back()
turtle.back()
endWell we have our two functions, and we have our pseudocode, so let's combine them into a finished program:
function placeCentral()
turtle.placeDown()
end
function buildArm()
turtle.forward()
turtle.placeDown()
turtle.forward()
turtle.placeDown()
turtle.forward()
turtle.placeDown()
turtle.back()
turtle.back()
turtle.back()
end
placeCentral()
buildArm()
buildArm()
buildArm()
buildArm()The proof is in the building though, so let's test it:
Well it builds the first arm correctly, but then it kinda just builds it 3 more times... Oh right, we forgot to tell it to turn right after building each arm. Let's add turtle.turnRight() to the end of buildArm():
function placeCentral()
turtle.placeDown()
end
function buildArm()
turtle.forward()
turtle.placeDown()
turtle.forward()
turtle.placeDown()
turtle.forward()
turtle.placeDown()
turtle.back()
turtle.back()
turtle.back()
turtle.turnRight()
end
placeCentral()
buildArm()
buildArm()
buildArm()
buildArm()And with that small hiccup out of the way it's now behaving as it should:
We automated + production, and the power of automation is the power of repetition. Instead of this just being a standalone program we can turn it into a function:
function buildPlus()
placeCentral()
buildArm()
buildArm()
buildArm()
buildArm()
endAnd then we can use this function to build multiple pluses stacked on top of each other:
buildPlus()
turtle.up()
buildPlus()
turtle.up()
buildPlus()
turtle.up()
buildPlus()Assembling this into a complete program we have:
function placeCentral()
turtle.placeDown()
end
function buildArm()
turtle.forward()
turtle.placeDown()
turtle.forward()
turtle.placeDown()
turtle.forward()
turtle.placeDown()
turtle.back()
turtle.back()
turtle.back()
turtle.turnRight()
end
function buildPlus()
placeCentral()
buildArm()
buildArm()
buildArm()
buildArm()
end
buildPlus()
turtle.up()
buildPlus()
turtle.up()
buildPlus()
turtle.up()
buildPlus()With this as the result:
I think this neatly demonstrates the power of functions. Each of these functions is called multiple times, so if we were to write the above code without functions it would be 167 lines of code, whereas we wrote it with 29 lines of code.
And more than that, this code is much easier to understand than a big pile of individual commands would be, and it's also much easier to modify. For example if we wanted our pluses to be larger all we would have to do is modify buildArm() to build 1 more block out, whereas if we were doing it the manual way we'd have to do that same change but in 16 different places.
Now that we saw functions in action it's time to talk about the technicalities surrounding them. The first such technicality is what kind of name you're allowed to give to a function and its arguments. The rules for function names and argument names are exactly the same so I'll just refer to them as "the rules for function names".
Function (and argument) names can only include lowercase letters (a-z), uppercase letters (A-Z), digits (0-9), and underscores (_). No other symbols are allowed. Additionally the first symbol cannot be a digit (otherwise Lua will think you're referring to a number).
Spaces are not allowed in function names, so if you want your function name to contain more than one word you need to mash all of the words together in some way. There's two standard ways to do this:
- snake_case: All of the words are joined together via underscores.
- camelCase: All of the words are just mashed together but every word after the first is capitalised.
In Lua camelCase is the widely used form and is what I've been exclusively using for this whole tutorial (placeCentral, buildPlus, etc.) but if you prefer snake_case (or some other format) you can use that instead.
There's actually one more rule for function names and it's that you cannot name a function after a Lua keyword, aka. you cannot name a function while or if or end or function or any of the other keywords.
Using an editor with syntax highlighting (aka. colors) helps with determining if you accidentally did that.
When defining a function we implicitly state how many arguments it's meant to take by writing in the definition that many names for the argumnets. For example here's a function that's meant to take 2 arguments:
function twoArg(first, second)
print(first)
print(second)
endIt does what one would expect if we give it two arguments:
But what if we give it more? Well, they're ignored. No error or anything, just ignored.
And if we give it less? Well, that's where we finally see nil appear:
The unspecified argument is implicitly specified to be nil. We can also explicitly specify an argument to be nil:
So in essence our 2-argument function always receives 2 arguments no matter how many we give it.
The built-in functions are capable of returning values, and so are ours. Using the return keyword we can specify what we want the result of our function to be.
function myAdd(a, b)
if a == 9 and b == 10 then
return 21
else
return a + b
end
end
Apart from specifying the return value of our function, return does a second thing: it immediately ends the execution of the function (aka. the function immediately returns).
function earlyReturn(x)
if x then
return 7
else
end
print("a")
print("b")
return "c"
end
This can be rather handy on its own merit, which is why you can use return without any return value if you just want to use it to end the function execution early.
Functions can return multiple values. We've seen that already when turtle.forward() told us the turtle needs fuel:
To have return return multiple values, simply give it multiple values separated by commas.
function sorted(a,b)
if a < b then
return a, b
else
return b, a
end
endLua handles multi-value returns a bit weirdly so this is all I'll say for now and we'll dive into the details later.
Lua, if not told otherwise, executes lines of code one by one from top to bottom. This includes function definitions: The first function definition is created first, then the second, and so on. If there's code between the definitions it'll execute that code the moment it's reached. That code can call previously defined functions but not functions that haven't yet been defined.
function f()
......
end
f() -- works, f() is defined
g() -- errors, g() is not defined yet
function g()
......
endA big quality of life feature however is that you can define a function that uses another function that hasn't been defined yet:
function f()
g()
end
function g()
print("hi")
end
f() -- works, prints "hi"This works due to Lua trusting you when you define f() that you're going to define g() later. As long as you do that before calling f() everything works out. An error occurs if you try to call f() before defining g(), however:
function f()
g()
end
f() -- error, g() is not defined
function g()
print("hi")
endSo in summary you should define all of your functions before using them but you are free to define them in any order you want.
Wait, so if we can define f to call g before we've defined g, what happens if we define g to call f? Will it error? Will it infinitely loop? Well, let's try it. I'll also add prints so we can see what function is called when.
function f()
print("f called")
g()
end
function g()
print("g called")
f()
end
f()
It's a loop! The "too long without yielding" error is exactly what we would get from an empty while true do end loop as we don't have any actions in the loop that yield.
This kind of looping is called recursion, and in fact we could've done it more simply by just having f call itself:
function f()
print("f called")
f()
end
f()
We already have a way to do looping via while loops but let's explore these recursive loops anyway. The loop we made so far corresponds to a while true loop since it never ends, but if we make f only call itself when some condition is true we can have a full while loop.
As an example here's the recursive equivalent of while not turtle.detect() do turtle.forward() end:
function goToWall()
if not turtle.detect() then
-- not yet at wall, keep going
turtle.forward()
goToWall()
else
-- reached wall, stop looping
end
end
goToWall()That is a whole lot more code than the loop... But it does work.
We just discovered how to turn any while loop into a recursive version, but we already had perfectly good while loops. Can recursion do more?
Well, so far we've done recursion without giving the function any arguments, but maybe we ought to give it some. Let's start off simple and just give it a single argument that it increments on each iteration:
function counting(c)
print(c)
sleep(1) -- to make the result easier to see
counting(c+1)
end
counting(1)
Very interesting. This is an infinite loop so let's make it finite again by adding a condition. The fun part is that now the condition can depend on the count:
function countTo5(c)
if c == 5 then
print(c)
-- final number, no more counting
else
print(c)
sleep(1)
countTo5(c+1)
end
end
countTo5(1)
This is all stuff we were not able to do with just while loops until now.
Let's leave the realm of math and go back to controlling turtles. Our code can now count, so let's have our turtle go forward exactly 5 times. To make things a bit easier to implement I'll rename our counter to stepsTaken which will represent how many steps the turtle has taken already, and said counter will start from 0 instead of 1.
function forward5(stepsTaken)
if stepsTaken == 5 then
-- 5 whole steps, we're done
else
-- less than 5 steps, we need to take another one
turtle.forward()
-- and we keep going but now having taken 1 extra step
forward5(stepsTaken+1)
end
end
forward5(0)
Amazing. We've made ourselves a brand new kind of stopping condition for our turtles, which unlike turtle.detect() does not depend on the turtle's surroundings at all.
There's two problems with this code though. The first is that it goes exactly 5 steps forward, and if we want any other amount we have to write it again but with 5 changed to another number. The second problem is that the external call needs to supply the initial 0 which is kind of weird.
We can fix both of these problems by making one change: counting from 5 to 0 instead of from 0 to 5:
function forwardN(stepsLeft)
if stepsLeft == 0 then
-- 0 steps left to do, we're done
else
-- 1 or more steps left to do, let's take one
turtle.forward()
-- and then we keep going but with 1 fewer steps left to do
forwardN(stepsLeft-1)
end
end
forwardN(5)Now the argument that provides the initial count also doubles as a way to provide how many steps forward we want to go.