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