Conditions and loops
Make decisions with if, repeat work with while, repeat and for, and write the main loop of a program that never stops.
A program runs its lines from top to bottom. Conditions choose which lines run, loops run lines several times. With these two tools, a computer can watch a vault, count passing trains, or step a Mechanical Press line through its cycle. This page assumes you know values and variables.
for seconds = 3, 1, -1 do
print("airship launch in " .. seconds)
sleep(1)
end
print("liftoff!")airship launch in 3 airship launch in 2 airship launch in 1 liftoff!
if, elseif, else
if runs a block only when a condition is true:
local stock = 40
if stock < 64 then
print("low stock: start the Mechanical Press")
endlow stock: start the Mechanical Press
The shape is always if condition then ... end. The condition can be any expression; remember that only false and nil count as false (truth in Brass), so if 0 then runs.
else gives the block to run otherwise, and elseif adds more cases. Brass tests the conditions from the top and runs the block of the first one that is true; the others are skipped, even if they are true too. So order matters: test the strictest case first.
local fill = 0.85 -- the vault is 85 % full
if fill >= 1 then
print("FULL: stop the drills")
elseif fill >= 0.75 then
print("almost full")
elseif fill <= 0.1 then
print("almost empty")
else
print("ok")
endalmost full
Had fill >= 0.75 come first, a full vault would only say "almost full".
elseifis one word.else ifopens a secondifinside theelse, which then needs its ownend.if x = 5 thenis a compile error ('then' expected near '='): comparing is==.- Short blocks fit on one line:
if powered then rs.set("top", true) end. - Several conditions combine with
and,orandnot:if fuel > 0 and not stopped then.
while
while repeats a block as long as its condition is true. The condition is checked before each round, so the block may run zero times.
local items = 1000
local stacks = 0
while items >= 64 do
items = items - 64
stacks = stacks + 1
end
print(stacks .. " stacks, " .. items .. " left over")15 stacks, 40 left over
Something in the block must eventually make the condition false, or the loop never ends. That is sometimes what you want (see main loops), and Ctrl+T always stops a program.
repeat ... until
repeat ... until condition runs the block first and checks afterwards, so it always runs at least once. It stops when the condition becomes true (the opposite of while). The condition can use the local variables declared inside the block.
local tries = 0
repeat
tries = tries + 1
local moved = tries * 16 -- each try moves 16 items
until moved >= 64
print("done after " .. tries .. " tries")done after 4 tries
The classic use is waiting for something, checking every half second. Here, a car on an Inductive Loop Detector (inductive_loop.detected()):
local loop = peripheral.wrap("right")
repeat
sleep(0.5)
until loop.detected()
print("a car is waiting")Counting: the numeric for
for i = first, last do ... end runs the block once for each number from first to last, both included. A third number sets the step; a negative step counts down.
for floor = 1, 3 do
print("elevator at floor " .. floor)
end
for t = 10, 1, -4 do
write(t, " ")
end
print()
for x = 0, 1, 0.25 do
write(x, " ")
end
print()elevator at floor 1 elevator at floor 2 elevator at floor 3 10 6 2 0 0.25 0.5 0.75 1
What to know about it:
first,lastand the step are computed once, before the first round. Changinglastinside the loop does not change the number of rounds.- The loop variable is a local of the loop, a copy of the counter: changing it in the block does not change the next round. After
endit no longer exists. - When
firstis already pastlast, the block does not run at all:for i = 1, #liston an empty list is safe. - The step cannot be zero (
'for' step is zero), and all three values must be numbers ('for' initial value must be a number).
for i = 1, 3 do
i = i * 10 -- changes this round's copy only
print(i)
end
print(i) -- the loop variable is gone10 20 30 nil
A loop written at the top level of a file (outside any function) keeps its variable in a chunk-level slot, like any chunk-level local. A function created inside such a loop does not get its own copy of the variable: it reads the slot, which holds the last value once the loop is over. Inside a function, the same code is a compile error (see the scoping rule).
local handlers = {}
for i = 1, 3 do
handlers[i] = function() return i end
end
print(handlers[1](), handlers[3]())3 3
Going through a table: pairs, ipairs, for v in
To visit the elements of a table, use a for ... in loop:
| loop | visits |
|---|---|
for i, v in ipairs(list) do | positions 1, 2, 3... with their value, until the first nil |
for v in list do | the same values, without the position (a Brass shortcut) |
for k, v in pairs(t) do | every key and its value: the list part first, in order, then the other keys in the order they were added |
local players = {"Steve", "Alex", "Notch"}
for i, name in ipairs(players) do
print(i, name)
end
for name in players do
write(name, " ")
end
print()
local speeds = {press = 64, mixer = 128, saw = 32}
for machine, rpm in pairs(speeds) do
print(machine, rpm)
end1 Steve 2 Alex 3 Notch Steve Alex Notch press 64 mixer 128 saw 32
for k, v in t(a table with two variables) is an error:use pairs() or ipairs() to iterate a table with two variables. Choose:ipairsfor a list,pairsfor anything else._is the usual name for a variable you do not need:for _, item in ipairs(items) do.- A generic
fortakes at most two variables. - Changing or removing existing keys while
pairsruns is safe; keys added during the loop may not be visited. Removing elements from a list while going through it forwards skips elements: go backwards instead (see lists).
Leaving a loop: break
break leaves the loop it is in, right away, and the program continues after its end. It is how you stop searching once you have found what you were looking for. Here, the first empty slot of a 9-slot chest:
-- what chest.get(slot) returns for each slot: nil means empty
local slots = {}
slots[1] = {name = "minecraft:cobblestone", count = 64}
slots[2] = {name = "minecraft:cobblestone", count = 64}
slots[4] = {name = "minecraft:iron_ingot", count = 3}
local size = 9
local free = nil
for slot = 1, size do
if slots[slot] == nil then
free = slot
break
end
end
print("first empty slot: " .. tostring(free))first empty slot: 3
The loop counts up to size rather than #slots, because a table with gaps has no reliable length. With a real chest, the same loop reads for slot = 1, chest.size() do and tests chest.get(slot) == nil (inventory.size(), inventory.get()). If no slot is free, free stays nil, which is why it is printed through tostring.
break only leaves the innermost loop. And Brass has no continue: to skip the rest of a round, put that rest inside an if:
for _, item in ipairs({"coal", "", "iron", "gold"}) do
if item ~= "" then -- skips empty names
write(item, " ")
end
end
print()coal iron gold
break outside a loop is a compile error: 'break' outside a loop.
Loops inside loops
A loop can contain another loop: the inner one runs completely for each round of the outer one. That is how you go through a grid, rows then columns. A wall of lamps lit in a checkerboard:
for row = 1, 3 do
local line = ""
for col = 1, 8 do
if (row + col) % 2 == 0 then
line = line .. "#"
else
line = line .. "."
end
end
print(line)
end#.#.#.#. .#.#.#.# #.#.#.#.
To leave both loops at once, set a variable before the inner break and test it in the outer loop (or put the loops in a function and return from it):
local map = {
{"stone", "stone", "coal"},
{"stone", "diamond", "stone"},
{"iron", "stone", "stone"},
}
local found = nil
for row = 1, #map do
for col = 1, #map[row] do
if map[row][col] == "diamond" then
found = row .. "," .. col
break -- leaves the inner loop
end
end
if found then break end -- leaves the outer loop
end
print("diamond at " .. found)diamond at 2,2
Blocks: do ... end
do ... end makes a block and nothing else. Locals declared inside disappear at its end, which keeps temporary variables out of the way. At the top level of a file it has a useful side effect: a function defined inside can keep using the block's locals (they are chunk-level), while the rest of the file cannot see them.
do
local count = 0 -- hidden from the rest of the file
function next_ticket()
count = count + 1
return count
end
end
print(next_ticket(), next_ticket(), next_ticket())
print(count)1 2 3 nil
Stopping early
A return at the top level of a file ends the program, there and then. It is the simplest way to give up when something is missing, before the main part of the program:
local chest = peripheral.wrap("left")
if chest == nil then
print("no chest on the left")
return
end
print("watching " .. chest.size() .. " slots")return must be the last statement of its block, which is why it sits inside the if. error("no chest on the left") stops the program too, but prints the message in red as an error (see Errors). Inside a function, return leaves only that function: see Functions.
Main loops: programs that never end
Most programs in a world never finish: they watch, react, and start again. They are a while true do ... end loop around the work, with a pause in it. There are two kinds of pause.
Sleeping a fixed time with sleep, to check something regularly (a vault, a tank, the time of day):
local chest = peripheral.wrap("left")
while true do
local iron = chest.count("minecraft:iron_ingot")
rs.set("top", iron < 64) -- alarm lamp when iron runs low
sleep(5) -- look again in 5 seconds
endWaiting for an event with os.pull_event, to react the moment something happens (a redstone change, a key, a message, a timer). The computer sleeps until then:
local count = 0
while true do
os.pull_event("redstone") -- sleeps until a redstone input changes
if rs.get("left") > 0 then
count = count + 1
print("trains so far: " .. count)
end
endEvents are explained in Events, timers and clocks in Time. A loop over events can be tried without any block around: os.queue_event adds events to the computer's own queue.
os.queue_event("order", "iron")
os.queue_event("order", "gold")
os.queue_event("stop")
while true do
local event = os.pull_event()
if event.name == "stop" then break end
print("crafting " .. event.data)
end
print("orders done")crafting iron crafting gold orders done
It does not freeze the game or lag the server. Each computer runs a fixed budget of instructions per tick (1,200 for a Personal Computer at 256 RPM, see Computers); when the budget is spent, the program simply pauses until the next tick and goes on from there. An endless loop just spreads over time.
But it wastes the computer's time: a loop that checks a lever without pausing asks the same question hundreds of times per tick, while the lever can only change once per tick, and the server spends real time running it. A sleep, even a short one (sleep(0.05) is one tick), or an os.pull_event lets the computer rest until there is something to do. See Performance.
sleep counts in seconds and rounds up to whole ticks (a tick is 1/20 of a second), with a minimum of one tick.
A complete example: a sequencer
A sequencer turns redstone outputs on and off in a fixed order, like the sequencer example. The steps live in a list, and a for loop walks through it:
local steps = {
{side = "left", time = 1}, -- push the piston
{side = "right", time = 2}, -- run the press
{side = "top", time = 1}, -- open the gate
}
for i, step in ipairs(steps) do
print("step " .. i .. ": " .. step.side .. " for " .. step.time .. " s")
rs.set(step.side, true)
sleep(step.time)
rs.set(step.side, false)
endstep 1: left for 1 s step 2: right for 2 s step 3: top for 1 s
To repeat the cycle forever, wrap the for in while true do ... end. Adding a step is now adding a line to the list, not rewriting the program. That idea, keeping the data apart from the code that walks through it, comes back in Tables.