Conditions et boucles
Prendre des décisions avec if, répéter avec while, repeat et for, et écrire la boucle principale d'un programme qui ne s'arrête jamais.
Un programme exécute ses lignes de haut en bas. Les conditions choisissent quelles lignes s'exécutent, les boucles exécutent des lignes plusieurs fois. Avec ces deux outils, un ordinateur peut surveiller un coffre à objet renforcé, compter les trains qui passent ou faire tourner le cycle d'une chaîne de presses mécaniques. Cette page suppose que vous connaissez les valeurs et les variables.
for secondes = 3, 1, -1 do
print("décollage du dirigeable dans " .. secondes)
sleep(1)
end
print("décollage !")décollage du dirigeable dans 3 décollage du dirigeable dans 2 décollage du dirigeable dans 1 décollage !
if, elseif, else
if n'exécute un bloc que si une condition est vraie :
local stock = 40
if stock < 64 then
print("stock bas : démarrez la presse mécanique")
endstock bas : démarrez la presse mécanique
La forme est toujours if condition then ... end. La condition peut être n'importe quelle expression ; rappelez-vous que seuls false et nil comptent comme faux (le vrai et le faux en Brass) : if 0 then s'exécute.
else donne le bloc à exécuter sinon, et elseif ajoute d'autres cas. Brass teste les conditions depuis le haut et exécute le bloc de la première qui est vraie ; les autres sont sautées, même si elles sont vraies aussi. L'ordre compte donc : testez le cas le plus strict en premier.
local remplissage = 0.85 -- le coffre renforcé est plein à 85 %
if remplissage >= 1 then
print("PLEIN : arrêtez les perceuses")
elseif remplissage >= 0.75 then
print("presque plein")
elseif remplissage <= 0.1 then
print("presque vide")
else
print("ok")
endpresque plein
Si remplissage >= 0.75 venait en premier, un coffre renforcé plein dirait seulement « presque plein ».
elseifs'écrit en un mot.else ifouvre un secondifdans leelse, qui demande alors son propreend.if x = 5 thenest une erreur de compilation ('then' expected near '=') : comparer s'écrit==.- Les blocs courts tiennent sur une ligne :
if alimente then rs.set("top", true) end. - Plusieurs conditions se combinent avec
and,oretnot:if carburant > 0 and not arrete then.
while
while répète un bloc tant que sa condition est vraie. La condition est vérifiée avant chaque tour : le bloc peut donc ne jamais s'exécuter.
local objets = 1000
local piles = 0
while objets >= 64 do
objets = objets - 64
piles = piles + 1
end
print(piles .. " piles, " .. objets .. " en trop")15 piles, 40 en trop
Quelque chose dans le bloc doit finir par rendre la condition fausse, sinon la boucle ne s'arrête jamais. C'est parfois voulu (voir les boucles principales), et Ctrl+T arrête toujours un programme.
repeat ... until
repeat ... until condition exécute le bloc d'abord et vérifie ensuite : il s'exécute donc au moins une fois. Il s'arrête quand la condition devient vraie (l'inverse de while). La condition peut utiliser les variables local déclarées dans le bloc.
local essais = 0
repeat
essais = essais + 1
local deplaces = essais * 16 -- chaque essai déplace 16 objets
until deplaces >= 64
print("fini en " .. essais .. " essais")fini en 4 essais
L'usage classique est l'attente de quelque chose, en regardant toutes les demi-secondes. Ici, une voiture sur une Boucle de détection (inductive_loop.detected()) :
local boucle = peripheral.wrap("right")
repeat
sleep(0.5)
until boucle.detected()
print("une voiture attend")Compter : le for numérique
for i = premier, dernier do ... end exécute le bloc une fois pour chaque nombre de premier à dernier, les deux compris. Un troisième nombre fixe le pas ; un pas négatif compte à rebours.
for etage = 1, 3 do
print("ascenseur à l'étage " .. etage)
end
for t = 10, 1, -4 do
write(t, " ")
end
print()
for x = 0, 1, 0.25 do
write(x, " ")
end
print()ascenseur à l'étage 1 ascenseur à l'étage 2 ascenseur à l'étage 3 10 6 2 0 0.25 0.5 0.75 1
Ce qu'il faut savoir :
premier,dernieret le pas sont calculés une seule fois, avant le premier tour. Changerdernierdans la boucle ne change pas le nombre de tours.- La variable de boucle est une locale de la boucle, une copie du compteur : la modifier dans le bloc ne change pas le tour suivant. Après
end, elle n'existe plus. - Quand
premiera déjà dépassédernier, le bloc ne s'exécute pas du tout :for i = 1, #listesur une liste vide ne pose aucun problème. - Le pas ne peut pas valoir zéro (
'for' step is zero), et les trois valeurs doivent être des nombres ('for' initial value must be a number).
for i = 1, 3 do
i = i * 10 -- ne change que la copie de ce tour
print(i)
end
print(i) -- la variable de boucle n'existe plus10 20 30 nil
Une boucle écrite au premier niveau d'un fichier (hors de toute fonction) garde sa variable dans un emplacement du fichier, comme toute locale de fichier. Une fonction créée dans une telle boucle n'a pas sa propre copie de la variable : elle lit l'emplacement, qui contient la dernière valeur une fois la boucle finie. Dans une fonction, le même code est une erreur de compilation (voir la règle de portée).
local actions = {}
for i = 1, 3 do
actions[i] = function() return i end
end
print(actions[1](), actions[3]())3 3
Parcourir une table : pairs, ipairs, for v in
Pour passer en revue les éléments d'une table, on utilise une boucle for ... in :
| boucle | parcourt |
|---|---|
for i, v in ipairs(liste) do | les positions 1, 2, 3... avec leur valeur, jusqu'au premier nil |
for v in liste do | les mêmes valeurs, sans la position (un raccourci de Brass) |
for k, v in pairs(t) do | chaque clé et sa valeur : la partie liste d'abord, dans l'ordre, puis les autres clés dans l'ordre où elles ont été ajoutées |
local joueurs = {"Steve", "Alex", "Notch"}
for i, nom in ipairs(joueurs) do
print(i, nom)
end
for nom in joueurs do
write(nom, " ")
end
print()
local vitesses = {presse = 64, mixeur = 128, scie = 32}
for machine, rpm in pairs(vitesses) do
print(machine, rpm)
end1 Steve 2 Alex 3 Notch Steve Alex Notch presse 64 mixeur 128 scie 32
for k, v in t(une table avec deux variables) est une erreur :use pairs() or ipairs() to iterate a table with two variables. Choisissez :ipairspour une liste,pairspour tout le reste._est le nom habituel d'une variable dont on n'a pas besoin :for _, objet in ipairs(objets) do.- Un
forgénérique prend au plus deux variables. - Modifier ou supprimer des clés existantes pendant un
pairsne pose pas de problème ; les clés ajoutées pendant la boucle peuvent ne pas être visitées. Retirer des éléments d'une liste en la parcourant vers l'avant en fait sauter : parcourez-la à l'envers (voir les listes).
Sortir d'une boucle : break
break quitte la boucle où il se trouve, tout de suite, et le programme reprend après son end. C'est ainsi qu'on arrête de chercher une fois qu'on a trouvé. Ici, le premier emplacement vide d'un coffre de 9 emplacements :
-- ce que coffre.get(emplacement) renvoie pour chaque emplacement : nil = vide
local emplacements = {}
emplacements[1] = {name = "minecraft:cobblestone", count = 64}
emplacements[2] = {name = "minecraft:cobblestone", count = 64}
emplacements[4] = {name = "minecraft:iron_ingot", count = 3}
local taille = 9
local libre = nil
for n = 1, taille do
if emplacements[n] == nil then
libre = n
break
end
end
print("premier emplacement vide : " .. tostring(libre))premier emplacement vide : 3
La boucle compte jusqu'à taille et pas jusqu'à #emplacements, car une table à trous n'a pas de longueur fiable. Avec un vrai coffre, la même boucle s'écrit for n = 1, coffre.size() do et teste coffre.get(n) == nil (inventory.size(), inventory.get()). Si aucun emplacement n'est libre, libre reste à nil, d'où le tostring à l'affichage.
break ne quitte que la boucle la plus intérieure. Et Brass n'a pas de continue : pour sauter la fin d'un tour, mettez cette fin dans un if :
for _, objet in ipairs({"charbon", "", "fer", "or"}) do
if objet ~= "" then -- saute les noms vides
write(objet, " ")
end
end
print()charbon fer or
break hors d'une boucle est une erreur de compilation : 'break' outside a loop.
Des boucles dans des boucles
Une boucle peut en contenir une autre : la boucle intérieure s'exécute en entier à chaque tour de la boucle extérieure. C'est ainsi qu'on parcourt une grille, les lignes puis les colonnes. Un mur de lampes allumées en damier :
for ligne = 1, 3 do
local texte = ""
for colonne = 1, 8 do
if (ligne + colonne) % 2 == 0 then
texte = texte .. "#"
else
texte = texte .. "."
end
end
print(texte)
end#.#.#.#. .#.#.#.# #.#.#.#.
Pour quitter les deux boucles d'un coup, remplissez une variable avant le break intérieur et testez-la dans la boucle extérieure (ou mettez les boucles dans une fonction et sortez-en avec return) :
local carte = {
{"pierre", "pierre", "charbon"},
{"pierre", "diamant", "pierre"},
{"fer", "pierre", "pierre"},
}
local trouve = nil
for ligne = 1, #carte do
for colonne = 1, #carte[ligne] do
if carte[ligne][colonne] == "diamant" then
trouve = ligne .. "," .. colonne
break -- quitte la boucle intérieure
end
end
if trouve then break end -- quitte la boucle extérieure
end
print("diamant en " .. trouve)diamant en 2,2
Les blocs : do ... end
do ... end crée un bloc, rien de plus. Les locales déclarées dedans disparaissent à son end, ce qui range les variables temporaires. Au premier niveau d'un fichier, il a un effet bien pratique : une fonction définie dedans peut continuer à utiliser les locales du bloc (ce sont des locales de fichier), alors que le reste du fichier ne les voit pas.
do
local compte = 0 -- caché au reste du fichier
function ticket_suivant()
compte = compte + 1
return compte
end
end
print(ticket_suivant(), ticket_suivant(), ticket_suivant())
print(compte)1 2 3 nil
S'arrêter plus tôt
Un return au premier niveau d'un fichier termine le programme, sur-le-champ. C'est la façon la plus simple d'abandonner quand quelque chose manque, avant la partie principale du programme :
local coffre = peripheral.wrap("left")
if coffre == nil then
print("pas de coffre à gauche")
return
end
print("je surveille " .. coffre.size() .. " emplacements")return doit être la dernière instruction de son bloc, d'où sa place dans le if. error("pas de coffre à gauche") arrête aussi le programme, mais affiche le message en rouge, comme une erreur (voir Erreurs). Dans une fonction, return ne quitte que cette fonction : voir Fonctions.
Les boucles principales : des programmes qui ne finissent jamais
La plupart des programmes d'un monde ne se terminent jamais : ils surveillent, réagissent, et recommencent. C'est une boucle while true do ... end autour du travail, avec une pause dedans. Il y a deux sortes de pause.
Dormir un temps fixe avec sleep, pour vérifier quelque chose régulièrement (un coffre renforcé, un réservoir, l'heure du jour) :
local coffre = peripheral.wrap("left")
while true do
local fer = coffre.count("minecraft:iron_ingot")
rs.set("top", fer < 64) -- lampe d'alarme quand le fer manque
sleep(5) -- on regarde à nouveau dans 5 secondes
endAttendre un évènement avec os.pull_event, pour réagir dès que quelque chose se passe (un changement de redstone, une touche, un message, un minuteur). L'ordinateur dort jusque-là :
local compte = 0
while true do
os.pull_event("redstone") -- dort jusqu'à ce qu'une entrée de redstone change
if rs.get("left") > 0 then
compte = compte + 1
print("trains passés : " .. compte)
end
endLes évènements sont expliqués dans Évènements, les minuteurs et les horloges dans Le temps. Une boucle d'évènements s'essaie sans aucun bloc autour : os.queue_event ajoute des évènements dans la file de l'ordinateur lui-même.
os.queue_event("commande", "fer")
os.queue_event("commande", "or")
os.queue_event("fin")
while true do
local evenement = os.pull_event()
if evenement.name == "fin" then break end
print("fabrication : " .. evenement.data)
end
print("commandes terminées")fabrication : fer fabrication : or commandes terminées
Elle ne fige pas le jeu et ne fait pas ramer le serveur. Chaque ordinateur exécute un budget fixe d'instructions par tick (1 200 pour un Micro-ordinateur à 256 tr/min, voir Ordinateurs) ; quand le budget est épuisé, le programme se met simplement en pause jusqu'au tick suivant et reprend là où il en était. Une boucle sans fin s'étale juste dans le temps.
Mais elle gaspille le temps de l'ordinateur : une boucle qui surveille un levier sans pause pose la même question des centaines de fois par tick, alors que le levier ne peut changer qu'une fois par tick, et le serveur passe du vrai temps à l'exécuter. Un sleep, même court (sleep(0.05) dure un tick), ou un os.pull_event laisse l'ordinateur se reposer jusqu'à ce qu'il y ait quelque chose à faire. Voir Performances.
sleep compte en secondes et arrondit au tick supérieur (un tick dure 1/20 de seconde), avec un minimum d'un tick.
Un exemple complet : un séquenceur
Un séquenceur allume et éteint des sorties de redstone dans un ordre fixé, comme l'exemple sequencer. Les étapes sont rangées dans une liste, et une boucle for la parcourt :
local etapes = {
{cote = "left", duree = 1}, -- pousser le piston
{cote = "right", duree = 2}, -- faire tourner la presse
{cote = "top", duree = 1}, -- ouvrir la porte
}
for i, etape in ipairs(etapes) do
print("étape " .. i .. " : " .. etape.cote .. " pendant " .. etape.duree .. " s")
rs.set(etape.cote, true)
sleep(etape.duree)
rs.set(etape.cote, false)
endétape 1 : left pendant 1 s étape 2 : right pendant 2 s étape 3 : top pendant 1 s
Pour répéter le cycle à l'infini, entourez le for d'un while true do ... end. Ajouter une étape revient maintenant à ajouter une ligne à la liste, pas à réécrire le programme. Cette idée, séparer les données du code qui les parcourt, revient dans Tables.