Skip to content

Instantly share code, notes, and snippets.

@umnikos
Last active May 15, 2026 21:14
Show Gist options
  • Select an option

  • Save umnikos/3ec51ee981496f2721e387e3896d6730 to your computer and use it in GitHub Desktop.

Select an option

Save umnikos/3ec51ee981496f2721e387e3896d6730 to your computer and use it in GitHub Desktop.
Introduction to Programming with ComputerCraft

Introduction to Programming with ComputerCraft

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.

image

Then right click on the turtle to open its UI.

image

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:

image

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.

Basics of Lua

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:

image

Specifically this mines the block in front of the turtle, there's also turtle.digDown() and turtle.digUp() for the other two directions:

image

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.

image

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:

image

Let's now try moving the turtle. Moving forward is done via turtle.forward():

image

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.

image

And after that we can call turtle.refuel()

image

Now we should be able to move forward with turtle.forward():

image

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

image

Getting loopy

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:

image

And with that I'd like to congratulate you on making a fully working cobblestone generator.

image

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:

image

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.

image

There's a slight problem though, when it runs out of blocks it keeps going...

image

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:

image image

Promising. Now, what we need is a true/false statement and not a number, but we can easily fix that with some math:

image image

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".

Saving our programs

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

image

From here we'll now open a text editor in which we can write our program. Type edit bridge.lua and press enter:

image image

Welcome to the built-in text editor. It's very barebones but it'll do the trick. Write down your program.

image

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:

image

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:

image

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...

image

...then press enter to save the file...

image

...then press control to open the menu again...

image

...then right arrow twice (or once on a non-advanced turtle) to navigate to "exit"...

image

...then press enter to exit the editor.

image

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:

image

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:

image

Exercise #1: Concrete maker

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: image
while true do
  turtle.place()
  turtle.dig()
end

This definitely works and would earn you full points. Another solution is the following setup with two turtles and two programs:

image
while true do
  turtle.place()
end
while true do
  turtle.dig()
end

While 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.

Checkpoint #1

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.

Interlude: Startup file

Sometimes when you leave a turtle to do something and then come back you can catch it slacking on the job:

image

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:

image image image image

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:

image

Getting iffy

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...
end

Again 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.

Lights controlled by the time of day.

Lamps activated by inverted daylight detectors are a classic.

image

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:

image

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
  ......
end

We 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()
end

Of 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:

image

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)
end

We're done... right? This sure is code at least, so let's run it.

image

...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
end

Testing this program it seems to work!... For about 10 seconds. Then it errors.

image

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)
end

Finally, some working code.

image

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)
end

This 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.

Interlude: Comments

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
end

How 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.

Tools for thought

Let's revisit our bridge building program. Here is the code we have so far:

while turtle.getItemCount() > 0 do
  turtle.forward()
  turtle.placeDown()
end

If 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.

Technique #1: Flowcharts

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:

image

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:

image

To go closer to lua we first replace all of the english actions with appropriate function calls:

image

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:

image

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
end

Flowcharts 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.

Technique #2: Pseudocode

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

Assuming 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()
end

And 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
end

And 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
end

Ta-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
end

Which 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)
end

Which 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.

Exercise #2: Tree farm

Create a program that automatically chops down and replants a tree over and over in order to automate wood production. Specifically your program should:

  1. Wait for the sapling to grow up into a tree
Screenshot From 2026-03-29 18-34-04 image
  1. Chop up the tree block by block
image image image
  1. Go back down to its original position
image
  1. Plant another sapling
image
  1. 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.

image

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

And 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)
end

Next 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()
end

Putting it all together we get:

-- chopUpTree()
turtle.dig()
turtle.forward()
while turtle.detectUp() do
  turtle.digUp()
  turtle.up()
end

Next 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()
end

A 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.

Checkpoint #2

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...

On numbers, booleans, and strings

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".

image

We can confirm that these values are in fact called "booleans" using the type() function:

image

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:

image

And using and and or we can combine them:

image image

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:

image

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.

image

Strings are pretty handy. In fact that's what type() has been returning this whole time

image

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.

image

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":

image

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:

image

We can also do slightly less basic math. ^ is exponentiation:

image

And % is the "modulo" operator. a % b is the remainder of dividing a by b if it was done as whole number division:

image

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:

image

These operators test for strict inequality. For non-strict inequality (aka. ≤ and ≥) we have <= and >=:

image

Additionally there's the equality (aka. =) operator == and the inequality (aka. ≠) operator ~=:

image

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:

image

Interlude: The weirdness of floats

Math stops mathing really quickly when we're dealing with floating-point numbers:

image

The above expression is a rather famous example and often gathers a lot of "what! why! how!" before the trick is revealed:

image

Wait no, that can't be right. Holdup, we'll need to go to a different version of lua...

image

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!

image

Or does it? Let's try bigger numbers:

image

Haha it does exist, and is in fact the exact same problem!

image

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...

image

At some point we reach infinity. There's a second way to reach infinity, and that's by dividing by zero:

image

You were probably told infinity is not a number. That's correct, it's not a number, but in lua it is:

image

You can do all sorts of fun things with infinity:

image

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...

image

"NaN". This is short for "not a number". Except that's false advertising because it's a number:

image

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:

image

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...

image

NaN is the only value that is unequal to itself. All NaNs are special and no two NaNs are created equal.

Takeaways

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:

  1. Fractional numbers are stored inaccurately
  2. Whole numbers are stored accurately, unless they get large in which case they're again stored inaccurately
  3. Dividing by zero and other math poo-poos yield garbage instead of erroring in any way
  4. 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.

Interlude: Cloud Catcher

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:

image

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.

image

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:

image

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.

image

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:

image image

We are now editing the file in our browser. Let's write some code to see what it looks like.

image

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:

image

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.

image

The program we wrote is infinite so to stop it we can use this button on the bottom right:

image

Now here's another cool part, we can edit multiple programs simulatenously by just running cloud edit again:

image image

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.

Alternate tool: CraftOS-PC Remote

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.

Debugging with print()

The function print() takes a string (or number) and displays it on the screen:

print("here's an example")
image

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)
end

And 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)
end

Then when we run this program we can see exactly what the turtle is doing when it has no blocks:

image

And what it's doing when it does have blocks:

image

As well as what it's doing when it had blocks but ran out:

image

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.

Making our own functions

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...
end

functionName 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:

image

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)
image

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.

Building a + sign

Turtles are really useful for automating building. As a simple exercise to get us started let's automate building a + sign:

image

Our turtle is going to start in the middle of the +

image

It's then going to place the central block:

image

And then build each arm of the + sign one by one:

image image image image

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

But 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:

image

So what we want is for the turtle to go forward and place down a block 3 times:

image

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:

image

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

Well 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:

image image image image image image image image

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:

image image image image image image

A tower of pluses

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

And 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:

image

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.

Rules for naming functions

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:

  1. snake_case: All of the words are joined together via underscores.
  2. 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.

image image

Differing number of arguments

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)
end

It does what one would expect if we give it two arguments:

image

But what if we give it more? Well, they're ignored. No error or anything, just ignored.

image

And if we give it less? Well, that's where we finally see nil appear:

image

The unspecified argument is implicitly specified to be nil. We can also explicitly specify an argument to be nil:

image

So in essence our 2-argument function always receives 2 arguments no matter how many we give it.

return keyword

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
image

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
image

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:

image

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
end

Lua handles multi-value returns a bit weirdly so this is all I'll say for now and we'll dive into the details later.

Function definition order

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()
  ......
end

A 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")
end

So in summary you should define all of your functions before using them but you are free to define them in any order you want.

Circularly referential functions

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

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.

Looping via recursion

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

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.

Doing more with recursion

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)
image

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)
image

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)
image

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment