Skip to content

Loops

Lesson 4.2

A loop runs the same code many times. Lua has while, repeat, and two kinds of for.

The numeric for

When you know how many times to repeat, use for:

for i = 1, 3 do
  print("Wave " .. i)
end
Wave 1
Wave 2
Wave 3

i starts at the first number and goes up by 1 until it reaches the second number. Both ends are included. A third number sets the step:

for y = 64, 60, -2 do
  print(y)
end
64
62
60

Some things to know:

  • The loop variable (i, y) is local to the loop. It doesn’t exist after end.
  • The start, end and step are worked out once, before the loop starts.
  • Don’t change the loop variable inside the loop. If you need a different value, copy it into another local.
  • If the start is already past the end, the body never runs: for i = 5, 1 do does nothing.

The generic for

The second kind of for walks over the items of a collection. You will use it all the time with tables:

local hotbar = { "Sword", "Pickaxe", "Torch" }

for slot, item in ipairs(hotbar) do
  print(slot, item)
end
1	Sword
2	Pickaxe
3	Torch

ipairs goes over a list in order, and pairs goes over every key of a table. The Tables chapter explains both in detail. Strings have a loop helper too: for word in s:gmatch("%a+") do (see String patterns).

while

while repeats as long as a condition is true. It checks the condition before each round:

local hunger = 3
while hunger < 20 do
  hunger = hunger + 8   -- eat a steak
  print("Hunger: " .. hunger)
end
Hunger: 11
Hunger: 19
Hunger: 27

Notice that hunger ended above 20: the check happens only at the top of each round. If you want a cap, use math.min(hunger + 8, 20).

Infinite loops

If the condition never becomes false, the loop never ends and the program hangs. Make sure something inside the loop moves it towards the end. In a Celestial mod, a script that runs too long is stopped by the game, but your mod will still misbehave.

repeat … until

repeat checks the condition after each round, so the body always runs at least once. The loop stops when the condition becomes true:

local tries = 0
repeat
  tries = tries + 1
  local roll = tries * 3   -- pretend this is a random roll
until roll >= 7

print(tries)  --> 3

Locals declared inside the body can be used in the until condition, as roll is here.

break

break leaves the loop right away:

local chest = { "Dirt", "Stick", "Diamond", "Apple" }

for i, item in ipairs(chest) do
  if item == "Diamond" then
    print("Found a diamond in slot " .. i)
    break
  end
end
--> Found a diamond in slot 3

break only leaves the innermost loop.

Skipping a round with goto

Lua has no continue. To skip to the next round, use goto with a label. A label is a name between double colons:

for i = 1, 5 do
  if i % 2 == 0 then
    goto continue
  end
  print(i)
  ::continue::
end
1
3
5

goto jumps to the label. Keep it for this one pattern. Jumping around freely makes code very hard to follow. Often an if around the rest of the body is just as clear:

for i = 1, 5 do
  if i % 2 ~= 0 then
    print(i)
  end
end

Loops inside loops

Loops can be nested. A classic use is walking over a grid:

for x = 1, 3 do
  local row = ""
  for z = 1, 3 do
    row = row .. (x == z and "#" or ".")
  end
  print(row)
end
#..
.#.
..#

Try it

Print a 5-step countdown from 5 to 1, then Go!. After that, write a while loop that doubles a number starting at 1 and prints each value, stopping before it goes over 64.

Show a solution
for i = 5, 1, -1 do
  print(i)
end
print("Go!")

local n = 1
while n <= 64 do
  print(n)
  n = n * 2
end

The second loop prints 1, 2, 4, 8, 16, 32 and 64.

Recap: use for i = a, b, step to count, for ... in to walk collections, while/repeat for unknown counts, break to leave, and goto continue to skip.