017 Übung lua i:
In unserer ersten Annäherung an »Lua« beschäftigen wir uns mit verschiedenen »Variablentypen« (unter anderem »Zeichenketten« und »Tabellen«) und sehen uns sogenannte »Schleifen (Iterationen)« an. In späteren Sitzungen kommen wir auch noch zu den Themen »Verzweigung« und »Funktionen«:
Diese Lua-Einführung beschränkt sich bewusst in vielen Fällen auf das, was wir später in unserer Mod-Programmierung benötigen. Es fallen also einige Dinge unter den Tisch, an manchen Stellen sind die Formulierungen zugunsten der Verständlichkeit ungenau oder der größere Kontext wird verschwiegen. Möchtest Du es aber ganz genau wissen, schaue Dir die offizielle Lua-Webseite an: lua.org
Am Ende der Einführung kannst Du dann hoffentlich nicht nur erste eigene Ideen umsetzen, sondern kommst ganz nebenbei dem Geheimnis der vielen Klammern und Kommas auf die Spur, die in »Minetest« immer wieder zu Fehlermeldungen führen:
Vorbereitung: Download
Falls Du es nicht schon gemacht hast, lade Dir »ZeroBrane Studio« herunter und installiere die Software in Deinem Programmordner:
Denke immer daran, zwischendurch zu speichern. Ein Sternchen (3) zeigt an, dass die Datei seit der letzten Bearbeitung nicht mehr gesichert wurde.
Verwendung von »Lua« in der »Windows PowerShell« oder mit »VS Code«:
Du willst »Lua« direkt in der »Windows PowerShell« oder mit »VS Code« verwenden? Dann sieh Dir die beiden nachfolgenden Videos an (englisch):
Für »VS Code« solltest Du noch die Erweiterung »Minetest Tools« installieren.
Bitte beachten: »Minetest« verwendet »Lua« in der Version 5.1. Für unsere Übungen spielt das aber im Moment keine Rolle.
Variablentypen: Auswahl
Wie jede gute Programmiersprache (oder in diesem Falle genauer »Skriptsprache«) verfügt auch »Lua« über diverse Variablentypen. Die wichtigsten im Zusammenspiel mit »Minetest« sind für unsere Übungen:
Zeichenkette (string): "Mit Anführungszeichen oben bin ich eine Zeichenkette" Zahl (number – integer oder float): 3 oder 3.141 Wahrheitswert (boolean): true/false – in manchen Sprachen auch 0/1, aber nicht in Lua Tabelle (table): {1, 2, 3} oder {"Apfel", "Birne", "Kirsche"} oder ... nil: es gibt an der gesuchten Stelle keinen sinnvollen oder passenden Wert (= false)
a = "Hallo Welt"
Achtung: »Lua« ist »case-sensitiv«. Es wird also zwischen Groß- und Kleinschreibung unterschieden. Eine Variable »a« (Kleinbuchstabe) bezeichnet folglich etwas anderes als eine Variable »A« (Großbuchstabe).
Kleiner Vorgriff zum Thema initialisieren:
Im Laufe der Programmierung gibt es immer wieder einmal die Situation, dass eine Variable der Wert einer anderen Variablen zugewiesen werden soll. Wenn man sich aber nicht sicher sein kann, ob die zuweisende Variable selber bereits einen Wert hat, hilft dieser Trick:
local b = a or "Standardwert falls a keinen Wert hat"
Auch diese Art der Initialisierung wird Dir manchmal begegnen:
local a, b, c = "Variable a", "Variable b", 3
local a = "Hallo Welt"
»Reserviertes Schlüsselwort«? Was ist das denn? Variablennamen folgen einer Namenskonvention. Folgende Ausdrücke darfst Du nicht als Bezeichner für Deine »Variablen« verwenden, weil sie bereits von »Lua« selbst eingesetzt werden:
and break do else elseif end false for function goto if in local nil not or repeat return then true until while
Spezialwissen für Neugierige:
Theoretisch wäre ein Unterstrich am Anfang einer »Variable« erlaubt. Darauf solltest Du aber möglichst verzichten, weil man »Variablen«, die mit einem Unterstrich beginnen, oft in besonderen Zusammenhängen verwendet.
Es hat sich eingebürgert, die »Bezeichner« immer klein zu schreiben und englische Begriffe zu verwenden.
Tipp: Oft – aber nicht immer – macht es Sinn, benötigte »Variable« ganz zu Beginn eines Skriptes zu initialisieren. Auf die Ausnahmen kommen wir bei Gelegenheit. Das Schlüsselwort »local« musst Du nur einmal beim Anlegen der »Variablen« nennen; danach kannst Du mit dem »Bezeichner« auf den Inhalt der »Variable« zugreifen.
Erste Codezeilen: print()
local a = "Hallo Welt" print(a)
local a = "Hallo Welt" print(a) a = 5 print(a)
Rechnen mit Variablen:
local a = 5 local b = 3 local c = a + b print(c)
local a = 5 local b = 3 print(a + b)
Lösung – aber bitte probiere zuerst selber einen Quellcode zu schreiben:
local x = 7
local y = 4
print(x - y)
print(x * y)
print(x / y)
AUFGABE: Hast Du schon einmal von »Modulo« gehört? Sieh Dir auf educba.com an, wie man damit in »Lua« rechnet.
Zeichenketten verknüpfen:
local first_string = "Hallo" local second_string = "Welt" local concatenated_string = first_string .. second_string print(concatenated_string)
Lösung – aber bitte probiere zuerst selber einen Quellcode zu schreiben:
local first_string = "Hallo"
local second_string = "Welt"
local concatenated_string = first_string .. " " .. second_string
print(concatenated_string)
AUFGABE: Wie kombinierst Du zwei »Variablen«, wenn die eine »Variable« eine Zahl enthält und die andere eine »Zeichenkette«?
Lösung – aber bitte probiere zuerst selber einen Quellcode zu schreiben:
local a = 10
local b = "Der Wert der Variablen a ist: "
local c = b .. a -- Lua wandelt die Variable a beim Verknüpfen einfach in einen string um ...
print(c)
Mit den zwei Verknüpfungspunkten kannst Du nahezu beliebige Kombinationen von »Zeichenketten« – die nicht vorher in eine »Variable« verpackt wurden – und »Variablen« herstellen:
local last_name = "Müller" local first_name = "Thomas" print("Herr " .. last_name .. " trägt den Vornamen " .. first_name)
Spezialwissen zu »print()« für Neugierige:
Willst Du, dass »print()« eine Ausgabe mehrzeilig vornimmt, gibt es zwei Wege:
local a = "Dies ist der erste Satz."
local b = "Dies ist der zweite Satz."
print(a .. "\n" .. b)
Der Befehl »\n« ist ein Escape-Zeichen« und sorgt für den nötigen Zeilenumbruch. Du hättest übrigens »\n« auch schon beim Anlegen der Variablen verwenden können:
local c = "Dies ist der erste Satz.\nDies ist der zweite Satz."
print(c)
Die zweite Möglichkeit nutzt doppelte eckige Klammern:
print([[
Dies ist der erste Satz.
Dies ist der zweite Satz.
]])
Auch hier kann man die eckigen Klammern schon beim Anlegen der Variablen verwenden:
local d = [[Dies ist der erste Satz.
Dies ist der zweite Satz.]]
print(d)
Und da wir schon einmal dabei sind: Zeichenketten müssen nicht unbedingt in doppelten Anführungszeichen stehen, einfache tun es auch:
local e = 'Hallo Welt'
print('Die Variable e enthält: ' .. e)
Einfache Anführungszeichen sind nur dann von Vorteil, wenn man innerhalb eines String doppelte Anführungszeichen benötigt:
local f = 'Herr Müller sagt: "Hallo Welt."'
print(f)
Alternativ kann man reservierte Schlüsselzeichen auch oft mit dem Backslash-Zeichen maskieren:
local g = "Herr Müller sagt: \"Hallo Welt.\""
print(g)
Datentyp Tabelle:
local my_table = {}
local my_table = { "Apfel", "Birne", 42, "Hallo Welt", "" }
local my_table = {"Apfel", "Birne", 42, "Hallo Welt", ""}
Es gibt verschiedene Arten von »Tabelle«. Unser Beispiel oben ist eine »Tabelle« mit einem numerischen Index, das heißt, jedes Element in der »Tabelle« ist über einen Zahlenindex abrufbar; in »Lua« beginnt dieser Index für »Tabellen« immer bei 1. Will man nun auf ein bestimmtes Element der »Tabelle« zugreifen, schreibt man den Zahlenindex in eckige Klammern hinter die Tabellen-Variable:
local my_table = { "Apfel", "Birne", 42, "Hallo Welt", "" } local print_me = my_table[1] print(print_me)
print(my_table[1])
local my_table = { "Apfel", "Birne", 42, "Hallo Welt", "" } my_table[1] = "Kirsche" my_table[9] = "Zucker"
Aber Moment mal: Da klafft doch jetzt eine Lücke von Index 6 - 8? In der Tat. Würdest Du print(my_table[7]) schreiben, bekämest Du als Ausgabe »nil«. Der Wert »nil« sagt, dass an dieser Stelle nichts gefunden wurde. Lücken in »Tabellen« machen manchmal Probleme, wir können aber dieses Thema fürs erste ignorieren.
AUFGABE: Kopiere den obigen Quelltext und probiere verschiedene Varianten des Tabellen-Zugriffs in »ZeroBrane Studio« aus, indem Du Werte veränderst, ausgibst, wieder überschreibst und erneut ausgibst.
Verschachtelte Tabellen
local table_within = { -- Beginn äußere Tabelle { -- Beginn erste innere Tabelle "Apfel", "Birne", 42, "Hallo Welt", "" }, -- Ende erste innere Tabelle { -- Beginn zweite innere Tabelle 2022, "Regen", "Mai" } -- Ende zweite innere Tabelle } -- Ende äußere Tabelle
local table_within = { -- Beginn äußere Tabelle {"Apfel", "Birne", 42, "Hallo Welt", ""}, -- erste innere Tabelle {2022, "Regen", "Mai"} -- zweiter innere Tabelle } -- Ende äußere Tabelle
Innere »Tabellen« dürfen untereinander »asymmetrisch« sein, müssen also nicht den gleichen Aufbau aufweisen was die Anzahl der Elemente und die Art der Datentypen betrifft.
Ganz wichtig: Die einzelnen »Tabellen« innerhalb der großen »Tabelle« müssen mit einem Komma voneinander getrennt werden.
Und wie greift man jetzt auf die einzelnen Elemente der inneren »Tabellen« zu? Da die »Tabelle« verschachtelt ist, benötigst Du zwei Zahlenindexe. Der erste Zahlenindex zielt auf die gewünschte innere »Tabelle«, der zweite Index auf das Element in der anvisierten inneren »Tabelle«:
local table_within = { {"Apfel", "Birne", 42, "Hallo Welt", ""}, {2022, "Regen", "Mai"} } local print_me = table_within[2][3] print(print_me)
AUFGABE: Kopiere den obigen Quelltext und überschreibe in »ZeroBrane Studio« mit einem Indexzugriff in der ersten »Tabelle« das vierte Element mit einem Zahlenwert. Welche Möglichkeiten fallen Dir noch ein?
Exkurs: Minetest
Tipp: Falls der Quellcode nicht gut lesbar ist, lade ihn Dir mit Rechtsklick und »Bild speichern unter …« auf Deine Festplatte.
Immer dasselbe I: Schleifen
for i = 1, 10 do print(i) end
Wichtig: Jede Schleife wird mit einem »end« abgeschlossen.
AUFGABE: Kannst Du die Einträge der folgenden »Tabelle« mit Hilfe einer Schleife und einer print()-Anweisung ausgeben?
local my_table = { "Apfel", "Birne", 42, "Hallo Welt", "" }
Lösung – aber bitte probiere zuerst selber einen Quellcode zu schreiben:
local my_table = {
"Apfel",
"Birne",
42,
"Hallo Welt",
""
}
for i = 1, 5 do
print(my_table[i])
end
Du kannst übrigens den »Startwert« und auch die »Abbruchbedingung« vorher in »Variablen« schreiben:
local start_value = 1 local end_value = 10 local print_string = “Index: ” for i = start_value, end_value do print(print_string .. i) end
AUFGABE: Kannst Du die folgende verschachtelte »Tabelle« mit einer Schleife ausgeben, indem Du die inneren »Tabellen« mit der Zählervariablen i ansprichst und die Werte der inneren »Tabellen« mit den festen Indexwerten 1, 2 und 3:
local table_print = { {"1. Tabelle: Wert 1", "1. Tabelle: Wert 2", "1. Tabelle: Wert 3"}, {"2. Tabelle: Wert 1", "2. Tabelle: Wert 2", "2. Tabelle: Wert 3"} }
Lösung – aber bitte probiere zuerst selber einen Quellcode zu schreiben:
local table_print = {
{"1. Tabelle: Wert 1", "1. Tabelle: Wert 2", "1. Tabelle: Wert 3"},
{"2. Tabelle: Wert 1", "2. Tabelle: Wert 2", "2. Tabelle: Wert 3"}
}
for i = 1, 2 do
print(table_print[i][1])
print(table_print[i][2])
print(table_print[i][3])
end
-- eine etwas andere Herangehensweise lernst Du gleich mit dem Befehl »ipairs« kennen
Immer dasselbe II: Schleifen mit ipairs
Wie immer gibt es mehrere Wege, wenn man ein Programmierproblem lösen muss. Du könntest zum Bespiel mit dem Befehl »#my_table« die Länge der Tabelle ermitteln und so die Anzahl der Durchläufe bestimmen:
local my_table = {"Apfel", "Birne", 42,"Hallo Welt",""} local start_value = 1 local end_value = #my_table for i = start_value, end_value do print("Ausgabe: " .. i) end
Spezialwissen für Neugierige:
Auch »Zeichenketten« sind wie ein indexbasiertes array aufgebaut:
local my_string = "Hallo Welt"
print(my_string[1])
Folglich lässt sich die Zeichenkette »my_string« mit einer Schleife Buchstabe für Buchstabe ausgeben:
for i = 1, #my_string do
print(i)
end
Oftmals eleganter geht es aber mit dem Befehl »ipairs« für Tabellen an. Du kannst Dir den Begriff »ipairs« mit »Index-Paare« übersetzen:
local my_table = { "Apfel", "Birne", 42, "Hallo Welt", "" } for i, v in ipairs(my_table) do print("Index " .. i .. ": " .. v) end
AUFGABE FÜR MUTIGE: Verschachtelte »Tabellen« haben wir schon kennengelernt. Aber auch Schleifen lassen sich verschachteln (»nested«). Kannst Du die folgende verschachtelte »Tabelle« mit einer verschachtelten ipairs-Schleife auslesen:
local table_within = { {"Apfel", "Birne", 42, "Hallo Welt", ""}, {2022, "Regen", "Mai"} }
Lösung – aber bitte probiere zuerst selber einen Quellcode zu schreiben:
local table_within = {
{"Apfel", "Birne", 42, "Hallo Welt", ""},
{2022, "Regen", "Mai"}
}
for i, v in ipairs(table_within) do
for i, v in ipairs(v) do
print("Index " .. i .. ": " .. v)
end
end
Kommentare:
-- diese Zeile ist ein Kommentar und wird nicht ausgeführt ...
Einen Kommentar über mehrere Zeilen kannst Du auch wie folgt schreiben:
-- [[ Alles, was hinter den zwei Bindestrichen und innerhalb der eckigen Klammern steht, wird vom Interpreter als mehrzeiliger Kommentar verstanden und nicht ausgeführt. ]]
Willst Du noch etwas üben:
local input = io.read()
Lösung – aber bitte probiere zuerst selber einen Quellcode zu schreiben:
local input = {}
print("Bitte gib Deinen Namen ein:")
input[1] = io.read()
print("Bitte gib Deinen Wohnort ein:")
input[2] = io.read()
print("Hallo " .. input[1] .. " aus " .. input[2])
Warum ist die hier vorgestellte Lösung mit einer while-Schleife für die oben gestellte Aufgabe besser: maschinennah
AUFGABE: Was bewirken die folgenden Codezeilen? Kopiere sie in »ZeroBrane Studio«:
local my_string = "Hallo Welt" print(#my_string) local my_table = {"Apfel", "Birne", 42, "Hallo Welt", ""} print(#my_table) local integer = 42 local string = "8" print(integer + string) print(type(my_string)) print(type(my_table)) print(type(integer)) local is_true = true print(type(is_true)) local a, b, c = "Variable a", "Variable b", 3 print(a .. ", " .. b .. ", " .. c)
Lösung – aber bitte probiere zuerst selber einen Quellcode zu schreiben:
-- Tabellen anlegen und füllen
my_table = {} -- äußere Tabelle
maxRows = 3 -- Anzahl der inneren Tabellen
maxColumns = 3 -- Anzahl Einträge in den jeweiligen inneren Tabellen
for row = 1, maxRows do
my_table[row] = {} -- innere Tabelle anlegen
for col = 1, maxColumns do
my_table[row][col] = row .. "." .. col -- innere Tabelle füllen
end
end
-- Tabelle ausgeben mit for-Schleife
for row = 1, maxRows do
for col = 1, maxColumns do
print(my_table[row][col])
end
end
-- Tabelle ausgeben mit ipairs()
for first_i, v in ipairs(my_table) do
for second_i, v in ipairs(my_table[first_i]) do
print("pairs-Ausgabe: " .. my_table[first_i][second_i])
end
end