Benutzer-Werkzeuge

Webseiten-Werkzeuge


scripting:tutorials:level1:tables

Tables

Tables sind der letzte wichtige Datentyp, mit dem wir uns in diesem Tutorial beschäftigen wollen. In Tables können alle anderen Datentypen wie in einem Container gesammelt bzw. gruppiert werden. Sie dienen also dazu, beliebige Variablen, die zusammen gehören, in eine gemeinsame Struktur zu bringen (Vergleiche mit Funktionen, die Instruktionen zusammenfassen).

Tables haben dabei zwei verschiedene „Gesichter“: Sie können zum einen wie Listen benutzt werden, in denen nacheinander Variablen liegen. Zum anderen kann man sie auch wie „Wörterbücher“ verwenden, in denen Paare von Variablen einander zugewiesen werden. Diese zwei verschiedenen Arten von Tables werden wir im Folgenden nacheinander betrachten.


Tables als Listen

Jedes Table wird mit einem Paar geschweifter Klammern {} eingeleitet und beendet. Ein leeres Table wird so definiert:

MyTable = {}

Zwischen den Klammern können nun beliebige Variablen abgelegt werden, die durch ein Komma voneinander getrennt sind:

Variable1 = 42
Variable2 = "Hallo Welt"
CodingIsFun = true
 
MyTable = {Variable1, Variable2, CodingIsFun}

Dabei ist es nicht zwingend notwendig, dass die Variablen zuvor schon existieren (global oder lokal). Variablen können auch in einem Table neu deklariert werden:

-- Hier wird das gleiche Table wie im oberen Beispiel erstellt
MyTable = {42, "Hallo Welt", true}

Je nachdem, ob das Table global oder lokal ist, sind die darin erstellten Variablen ebenfalls global oder lokal.

Will man auf die Variablen in einer Table-Liste zugreifen, geschieht dies, indem man den Namen des Tables gefolgt von der Stelle (Index), an der die Variable steht, in eckigen Klammern [] aufschreibt:

MyTable = {42, "Hallo Welt", true}
 
print(MyTable[1])
print(MyTable[2])
print(MyTable[3])
 
-- Der Befehl print hat in Siedler 5 keine Funktion
-- Der Code muss also in der lua demo, die unten verlinkt ist, ausgeführt werden
-- Alternativ können alle print-Aufrufe auch durch Message ersetzt werden. Dann wird Text im Spiel angezeigt

Tables, die wie Listen funktionieren, werden auch als numerische Tables bezeichnet.


Hinweis: Unsere Beispiele in diesem und den vorigen Kapiteln können besser nachvollzogen werden, wenn man die Programmzeilen nebenbei ausprobieren kann. Dazu kann man diese Webseite hier verwenden:

https://www.lua.org/demo.html

Um dabei bei der Ausgabe ein Ergebnis zu sehen, kann man die Funktion print verwenden. Will man beispielsweise die Variable MyTable[2] ansehen, kann man

print(MyTable[2])

schreiben. Wenn die Variable einen anderen Namen hat, muss man diesen dementsprechend austauschen.


Hinweis: Falls du schonmal mit anderen Programmiersprachen gearbeitet hast, bist du möglicherweise gewohnt, dass die Indizes derartiger Listen bzw. Arrays bei 0 zu zählen beginnen. Lua ist da anders: Hier beginnen Listen immer bei Index 1!


Nutzung von Schleifen

Vielleicht hast du schon bemerkt, dass es sich anbietet, Schleifen zu verwenden, wenn man mehrere Einträge eines Tables braucht. Mit einer for-Schleife kann die Zählvariable dazu benutzt werden, die Table-Einträge nacheinander zu referenzieren:

-- Dieser Code ist äquivalent zum Beispiel darüber
MyTable = {42, "Hallo Welt", true}
 
for i = 1, 3 do
    print(MyTable[i])
end

Es gibt Umstände, unter denen du nicht weißt, wie groß ein Table ist. Es gibt Funktionen, die Listen variierender Größe als Rückgabewert haben. Da so ein Fall sehr oft eintritt, wollen wir eine sehr wichtige Funktion vorwegnehmen:
table.getn
Damit kann die Größe bzw. Länge einer Liste ermittelt werden. Dabei gibt es eine Einschränkung: Diese Funktion funktioniert nur wie erwartet bei Listen. Bei einigen Tables, die wir weiter unten beschreiben, funktioniert diese Funktion nicht. Wir werden entsprechend darauf hinweisen.

Das gleiche Beispiel kann nun unter Verwendung von table.getn noch etwas umgebaut werden:

MyTable = {42, "Hallo Welt", true, "Extra Eintrag"}
-- Ermittle die Größe von MyTable und speichere diese Größe (als Zahl) in MyTableSize ab
MyTableSize = table.getn(MyTable) -- #MyTable 
 
-- Lasse die Schleife nun immer so weit laufen, bis alle Einträge in MyTable angezeigt wurden
for i = 1, MyTableSize do
    print(MyTable[i])
end

In dem Beispiel haben wir hinter dem table.getn-Befehl #MyTable als Kommentar einfügt. Das liegt daran, dass Siedler eine alte Version von Lua benutzt, die verlinkte Demo die aktuellste. Die aktuelle Version von Lua ermittelt die Größe eines Tables mit #, in Siedler brauchst du allerdings table.getn. Wenn du den obigen Code in der Demo ausführen willst, musst du also MyTableSize = #MyTable schreiben.

Normalerweise braucht man für die Größe eines Tables keine eigene Variable, sodass sich der obige Code vereinfachen lässt:

MyTable = {42, "Hallo Welt", true, "Extra Eintrag"}
 
-- Lasse die Schleife immer so weit laufen, bis alle Einträge in MyTable angezeigt wurden
-- Setze das Ergebnis von table.getn direkt als Zielgröße ein
for i = 1, table.getn(MyTable) do -- #MyTable
    print(MyTable[i])
end

Daraus lassen sich dann nützliche Funktionen konstruieren. Beispielsweise lässt sich damit eine Funktion schreiben, die alle Zahlen in einer Liste aufaddiert:

function SumOfList(_List)
    local Sum = 0
    for i = 1, table.getn(_List) do --#_List
        Sum = Sum + _List[i]
    end
    return Sum
end
 
MyTable = {5, 8, 27, 11, 90, 3, 82}
MyTableSum = SumOfList(MyTable)


Eine Liste verändern

Konsequenterweise können wir über MyTable[2] das zweite Element unserer Liste nicht nur lesen, sondern auch überschreiben:

MyTable = {42, "Hallo Welt", true}
 
-- Schreibe an die zweite Stelle der Liste einen anderen Wert
MyTable[2] = "Welt, seid mir gegrüßt"

Auf diese Art und Weise kann ein Table auch nachträglich vergrößert werden, indem man einen Index benutzt, der die Listengröße um 1 erhöht:

MyTable = {42, "Hallo Welt", true}
 
-- Schreibe an die vierte Stelle der Liste einen neuen Wert
MyTable[4] = "Neuer Wert"

In einer Schleife können so beliebig große Listen erschaffen werden. In folgendem Beispiel erstellen wir ein Table, in dem die ersten paar Zahlen der Fibonacci-Folge liegen. Die gewünschte Länge unserer Liste geben wir als Parameter an:

function GetFibonacci(_Length)
    -- Falls man nur an höchstens einer Fibonacci-Zahl interessiert ist, brauchen
    -- wir nicht groß zu rechnen
    if _Length == 0 then
        return {}
    elseif _Length == 1 then
        return {0}
    end
 
    -- Initialisiere die Liste mit den ersten beiden Einträgen
    local FibonacciList = {0, 1}
    -- Die Schleife startet bei 3, denn die ersten beiden Einträge haben wir schon
    for i = 3, _Length do
        -- Wir greifen hier auf die Indizes i-2 und i-1 zu
        -- Wenn man auf diese Weise mit Indizes rechnet, muss man immer sicherstellen,
        -- dass man nicht "außerhalb" der Liste landet.
        -- Dadurch, dass wir mit zwei Elementen in der Liste beginnen, klappt das
        -- hier immer
        FibonacciList[i] = FibonacciList[i-2] + FibonacciList[i-1]
    end
 
    return FibonacciList
end
 
MyFibonacci = GetFibonacci(10)
 
-- Wir überlassen es dem Leser, die Liste MyFibonacci mit print komplett anzeigen zu lassen
-- Eine Schleife und table.getn wird dabei helfen

Achtung: Wenn Listen auf diese Art befüllt werden, ist es wichtig, dass die Indizes des Tables immer von 1 bis n gehen. In anderen Worten: Die Reihe der Indizes darf nicht unterbrochen sein! Andernfalls wird table.getn nicht mehr richtig funktionieren. Leider unterscheidet sich die Lua-Version von Siedler hier wieder von der aktuellen, weshalb folgendes Beispiel Message statt print verwendet:

MyTable = {6, 18}
 
-- Hier wird ein Wert an Stelle 4 geschrieben, obwohl Stelle 3 nicht existiert
MyTable[4] = 24
 
-- Als Table-Länge wird 2 angezeigt werden, weil danach ein "Loch" in der Liste ist
Message(table.getn(MyTable))
 
-- Wenn wir dieses "Loch" nun "stopfen"...
MyTable[3] = 1
 
-- wird die Table-Länge 4 angezeigt werden
Message(table.getn(MyTable))

Tables als Wörterbücher

Die andere Art von Tables sind solche, die Paare von Variablen einander zuordnet. Wie in einem Wörterbuch kann man in so einem Table für einen gesuchten Wert (Fremdwort, Schlüsselwert/Key) einen entsprechenden gespeicherten Wert (Wort in der Muttersprache, Value) vorfinden. Auch solche Tables werden mit geschweiften Klammern {} konstruiert. Im Gegensatz zur numerischen Variante werden diese Tables assoziative Tables genannt.

Dazwischen stehen dann die Werte-Paare. Der Schlüsselwert/Key liegt dabei in eckigen Klammern [] und wird durch ein =-Zeichen mit einem Wert/Value assoziiert. Ein sehr kleines Englisch-Deutsch-Wörterbuch sähe zum Beispiel so aus:

Dictionary = {
    ["Computer"] = "Rechner",
    ["Mouse"] = "Maus",
    ["Keyboard"] = "Tastatur",
    ["Screen"] = "Bildschirm"
}

Will man nun wissen, welches deutsche Wort einem englischen entspricht, kann man sich den Wert wie bei den Listen oben mit eckigen Klammern holen:

-- Was bedeutet Keyboard?
print("Keyboard bedeutet "..Dictionary["Keyboard"])

Natürlich kann man nicht nur Strings einander zuordnen:

NumberNames = {
    [1] = "One",
    [2] = "Two",
    [3] = "Three",
    [4] = "Four"
}

Der Name einer Zahl lässt sich dann mit NumberNames[i] ermitteln. Vielleicht fällt dir auf, dass diese Art der Referenzierung genau der von Listen entspricht. Und tatsächlich: Wenn die Keys des Tables ausschließlich Zahlen von 1 bis n sind, kann man dieses Table auch als Liste schreiben:

NumberNames = {"One", "Two", "Three", "Four"}

Es gibt für Tables eine weitere abkürzende Schreibweise, die sehr häufig benutzt wird. Wenn ein Key ein String ist, können die eckigen Klammern und Anführungszeichen weggelassen werden, sodass unser Wörterbuchbeispiel vereinfacht so aussieht:

-- Dieses kleine Wörterbuch ist genau das gleiche wie oben in einer anderen Schreibweise
Dictionary = {
    Computer = "Rechner",
    Mouse = "Maus",
    Keyboard = "Tastatur",
    Screen = "Bildschirm"
}

Wichtig: Das gilt nur für Strings und nur für Keys!

Die gleiche Vereinfachung existiert auch, wenn man den Wert anhand des Keys wieder referenzieren möchte:

-- Was bedeutet Keyboard?
print("Keyboard bedeutet "..Dictionary.Keyboard)

Der String-Key wird also mit einem Punkt vom Table-Name getrennt.

Hinweis: Bei Tables, die Key-Value-Paare benutzen, funktioniert table.getn nicht! Die Funktion wird für solche Tables immer 0 ausgeben.

Wichtig: In Siedler 5 müssen alle Keys entweder Strings oder Zahlen sein! Andernfalls gehen die Tables beim Speichern und Laden verloren.


Ein Table verändern

Wir können unserem Wörterbuch wie gewohnt neue Werte hinzufügen. Für Strings bleiben beide Schreibweisen:

-- Wir definieren einige String-Paare im Dictionary
Dictionary = {
    Computer = "Rechner",
    Mouse = "Maus",
    Keyboard = "Tastatur",
    Screen = "Bildschirm"
}
 
-- Jetzt fügen wir einige weitere hinzu:
Dictionary["Speaker"] = "Lautsprecher"
Dictionary["Headphones"] = "Kopfhörer"
 
-- Dabei können wir die Schreibweise frei wählen
Dictionary.Joystick = "Steuerknüppel"


Tables als Werte

Wenn man in einem Table weitere „Untertables“ einem Schlüsselwert zuordnet, hat das den Effekt, dass Tables „geschachtelt“ werden. Diese Konstruktion wird uns später beispielsweise in Briefings (FIXME link) begegnen.

Aus Rollenspielen kennst du vielleicht Charaktereditoren, mit denen man vornehmlich das Gesicht seines Spielcharakters anpassen kann. Die Eigenschaften des Spielcharakters können in einem geschachtelten Table gespeichert werden und so eine übersichtliche Struktur erhalten:

Character = {
    Hair = {
        Style = "Short Messy",
        Color = "Black",
        Effect = "Glossy"
    }, -- Key-Value-Paare werden weiterhin durch Kommas getrennt. Die werden oft vergessen
    Eyes = {
        Type = 9,
        Color = "Green",
        Size = 5,
        GapSize = 11
    },
    Nose = {
        Type = 3,
        Size = 7,
        Effect = "Dirt Patch"
    }
}

In diesem Beispiel haben wir oft „Type“ oder „Color“ als Key. Da diese aber immer einem anderen Teil des Gesichts zugewiesen sind, bleibt es eindeutig, worauf sie sich beziehen. Denn
Character.Eyes.Type
ist eine andere Variable als
Character.Nose.Type

Man kann dem geschachtelten Table wie gewohnt neue Werte hinzufügen:

Character.Nose.Height = 8
 
Character.Mouth = {
    Type = 10,
    Width = 15,
    Color = "Deep Red",
    Height = 4
}
scripting/tutorials/level1/tables.txt · Zuletzt geändert: 2023/09/07 11:15 von fritz_98