Back

Activity 1.6: Loops

divider

The Idea

Lua has three loops, and the numeric for is the one you will reach for most. It reads as from, to, by — and both ends are included.

main.lua
for i = 1, 5 do io.write(" ", i) end
print()
for i = 10, 1, -3 do io.write(" ", i) end
print()
for i = 0, 1, 0.25 do io.write(" ", i) end
print()
Output
1 2 3 4 5
10 7 4 1
0 0.25 0.5 0.75 1

for i = 1, 5 runs five times, ending on 5. There is no i < 5 to get wrong and no i++ to forget — Lua has no increment operator at all. The third number is the step, it may be negative, and it may be a fraction.

io.write is new here. It is like print with no tabs and no line break, which is what you want when building one line out of many pieces.

Two things the loop variable will not let you do. It only exists inside the loop, so you cannot read i after the end. And assigning to it inside the loop does not change how many times the loop runs — Lua worked the count out before the first pass.

When you do not know the count

while repeats for as long as its condition holds, and checks before each pass.

main.lua
local fuel = 10
while fuel > 0 do
io.write(" ", fuel)
fuel = fuel - 3
end
print()
Output
10 7 4 1

repeat ... until is the same idea upside down. It checks after each pass, so the body always runs at least once, and the condition says when to stop rather than when to continue.

main.lua
local tries = 0
repeat
tries = tries + 1
until tries >= 3
print("tries:", tries)
Output
tries: 3

Both of those conditions are inverted relative to each other. If you ever translate a while into a repeat, the condition has to flip, and forgetting that gives you a loop that runs exactly once or never stops.

Getting out early

break leaves the loop immediately, in any of the three kinds.

main.lua
for i = 1, 10 do
if i > 4 then break end
io.write(" ", i)
end
print()
Output
1 2 3 4

There is no continue in Lua. This is a genuine gap, and the accepted workaround is a jump to a label at the very bottom of the loop body.

main.lua
for i = 1, 6 do
if i % 2 == 0 then goto continue end
io.write(" ", i)
::continue::
end
print()
Output
1 3 5

goto continue jumps forward to ::continue::, skipping whatever sits between. The label is not a keyword and continue is not special — you could call it ::skip:: — but everyone writes it this way, so write it this way too.

The label has to be the last thing in the body. A label with code after it means you jumped past some lines and then ran others, which is not what you meant. And you can usually avoid the whole thing by inverting the test into if i % 2 ~= 0 then — reach for the jump when the alternative is three levels of nesting.

divider

Build

Start a fresh main.lua. You are running a launch sequence, burning the fuel it takes, and sweeping a row of checkpoints on the way out.


Task 1: Count down and launch

  • Count from 5 down to 1 with a numeric for and a negative step.
  • Print LIFTOFF after the loop has finished.
main.lua
-- main.lua - Skynest launch sequence
for i = 5, 1, -1 do
print("T-minus", i)
end
print("LIFTOFF")
Output
T-minus 5
T-minus 4
T-minus 3
T-minus 2
T-minus 1
LIFTOFF

Task 2: Burn fuel until it runs out

  • Start with 20 fuel and 0 distance. Each pass burns 6 fuel and adds 10 distance.
  • Loop while there is fuel left, then print both totals.
  • Look hard at the fuel figure. It is negative, and that is genuinely what the code you were asked for does. Work out why before reading on, then write the reason in a comment.
main.lua
-- main.lua - Skynest launch sequence
for i = 5, 1, -1 do
print("T-minus", i)
end
print("LIFTOFF")
local fuel = 20
local distance = 0
while fuel > 0 do
fuel = fuel - 6
distance = distance + 10
end
print("Fuel left:", fuel)
print("Distance:", distance)
Output
T-minus 5
T-minus 4
T-minus 3
T-minus 2
T-minus 1
LIFTOFF
Fuel left: -4
Distance: 40

The condition is checked before the pass, not during it. With 2 fuel left the loop still qualifies, then burns 6 anyway. A loop guard says whether to start a pass, never whether the pass can afford to finish.


Task 3: Scan the odd checkpoints only

  • Loop over checkpoints 1 through 8.
  • Skip the even ones using goto continue and a label.
  • Print a line for each checkpoint you actually scan.
main.lua
-- main.lua - Skynest launch sequence
for i = 5, 1, -1 do
print("T-minus", i)
end
print("LIFTOFF")
local fuel = 20
local distance = 0
while fuel > 0 do
fuel = fuel - 6
distance = distance + 10
end
print("Fuel left:", fuel)
print("Distance:", distance)
for checkpoint = 1, 8 do
if checkpoint % 2 == 0 then goto continue end
print("Scanning checkpoint", checkpoint)
::continue::
end

Challenge (Optional)

  • Fix Task 2 so the fuel never goes negative — the loop should only burn what it can afford.
  • Rewrite Task 3 with no goto at all. Two ways exist: invert the condition, or change the loop's step.
  • Convert Task 2's while into a repeat ... until that behaves the same. Remember the condition has to flip.
divider

Check Your Work

Your finished file should produce this.

Output
T-minus 5
T-minus 4
T-minus 3
T-minus 2
T-minus 1
LIFTOFF
Fuel left: -4
Distance: 40
Scanning checkpoint 1
Scanning checkpoint 3
Scanning checkpoint 5
Scanning checkpoint 7

You now have every control structure Lua has. Everything from here is about the one data structure you have not met yet — and once you have it, loops become the thing you do to it.