-
Gesamte Inhalte
5753 -
Benutzer seit
-
Letzter Besuch
Alle erstellten Inhalte von Goetz
-
Da ich gerade für jemanden ein kurzes Video erstellt habe, welches im Kern denselben Aspekt behandelt wie das Kapitel "Tabellen - Teil 6" in dieser Reihe hier, möchte ich das Video auch hier anbieten:
-
Schleifen - Teil 4 (für Fortgeschrittene) Die for … do Schleife kann man auch verwenden, um ganze Tabellen auszulesen: Beatles = {"John", "Paul", "George", "Ringo"} for i = 1, 4 do print("Beatle Nummer "..i.." ist "..Beatles[i]) end In Lua gibt es für diesen Zweck aber eine eigene, bessere Methode. Sie heißt in pairs(), weil sie paarweise den Index und den Inhalt zum Index liefert: Beatles = {"John", "Paul", "George", "Ringo"} for key, value in pairs(Beatles) do print("Beatle Nummer "..key.." ist "..value) end Die Namen der beiden Variablen für Index und Inhalt sind natürlich frei wählbar. Gebräuchlich sind die Buchstaben k und v für key (= Schlüssel) und value (= Wert). Deshalb habe ich im obigen Beispiel zum besseren Verständnis die Worte key und value gewählt. Die in pairs() Methode liefert alle Schlüssel/Wert Paare zurück. Auch solche, bei denen der Schlüssel keine Zahl, sondern ein String ist: Beatles = {John = "Gitarre", Paul = "Bass", George = "Gitarre", Ringo = "Schlagzeug"} for k, v in pairs(Beatles) do print(k.." spielt "..v) end Beachtet bitte, dass die Aufzählung nicht sortiert ist. Sie kann zufällig mit der Reihenfolge übereinstimmen, in der die Tabelle erstellt wurde. Aber sie muss es nicht und man weiß nie, in welcher Reihenfolge die Werte ausgelesen werden. Auch dann nicht, wenn die Indexe eine lückenlos nummerierte Aufzählung (wie im ersten Beispiel) sind. Aus diesem Grund gibt es eine zweite, sehr ähnliche Methode: in ipairs() Der Buchstabe i im Wort ipairs steht für „iterable“. Damit ist gemeint, dass nur lückenlos nummerierte Elemente der Tabelle ausgelesen werden und dass die Ausgabe sortiert stattfindet. Beatles = { "John", "Paul", "George", "Ringo", Ort = "Liverpool", Jahr = 1960, Manager = "Brian Epstein", Studio = "Abbey Road" } for k, v in ipairs(Beatles) do print("Schlüssel: "..k..", Wert: "..v) end Vergleicht die Ausgabe mal mit dem , was ihr bei in pairs() bekommt. Und lasst die in pairs() Version mehrfach laufen. Ihr werdet sehen, dass sich Reihenfolgen ändern, obwohl ihr das Skript zwischen den einzelnen Ausführungen nicht ändert. Der nummerierte Teil der Tabelle steht dabei für gewöhnlich vorne und ist sortiert. Aber garantiert ist auch das nicht! Wenn eine numerisch sortierte Aufzählung benötigt wird, dann muss man in ipairs() verwenden. Die Ausgabe von in ipairs() beginnt bei 1, endet bei der ersten Lücke in der nummerierten Liste und ignoriert namentliche Indexe. Beispiel = { Name = "Test", "Eins", "Zwei", "Drei", "Vier", "Fünf", "Sechs", "Sieben", "Acht" } Beispiel[6] = nil for k, v in ipairs(Beispiel) do print("Schlüssel: "..k..", Wert: "..v) end Eine fertige Methode für eine alphabetisch sortierte Aufzählung gibt es nicht.
-
Schleifen - Teil 3 Schleifen gehören zu den wichtigsten Hilfsmitteln in der Programmierung. Sie nehmen einem viel Arbeit ab und gerade im Zusammenspiel mit einer Software wie dem 3D-Modellbahnstudio sind das genau die Arbeiten, welche überhaupt den Einsatz eines Skripts rechtfertigen. Nehmt zum Beispiel einen Schattenbahnhof mit zehn Gleisen. Ein eintreffender Zug soll in eins dieser Gleise einfahren. Dazu muss geprüft werden, welche Gleise noch frei sind, um dann eine Fahrstraße zu einem dieser freien Gleise zu legen. Zunächst muss zehnmal dasselbe getan werden. Die Prüfung, ob ein Gleis besetzt ist. Und bei jedem der zehn Durchläufe muss genau ein Parameter geändert werden: Die Gleisnummer. Für solche Zwecke ist die for ... do Schleife gedacht. Zwischen die Schlüsselworte for und do schreibt man den Namen einer Variablen, ihren Anfangswert, ihren Schlusswert und den Abstand, in dem man von Anfang bis Schluss zählen möchte. Also zum Beispiel so etwas: for Gleis = 1, 10, 1 do Beachtet bitte das = Zeichen hinter der Variablen für die Zuweisung der Werte und die Kommas für die Trennung von Anfangswert, Schlusswert und Schrittweite. In den folgenden Zeilen steht dann das, was mehrmals hintereinander ausgeführt werden soll und am Ende des Blocks steht, wie bei Lua üblich, das Schlüsselwort end. Ich weiß noch nicht, wie man in V5 prüft, ob ein Gleis besetzt ist. Deshalb bleibe ich bei meinem bisherigen Prinzip, dass ich ersatzweise einfach etwas Text ausgebe: for Gleis = 1, 10, 1 do print("Ich prüfe, ob Gleis "..Gleis.." besetzt ist.") end Wenn ihr diese drei Zeilen bei Lua-Demo eingebt und laufen lasst, dann steht im Ausgabefenster die Textzeile zehnmal untereinander. Dabei steht an Stelle der Variablen Gleis in jeder Zeile eine andere Nummer. Das ist die Zahl, welche bei jedem neuen Durchlauf der Schleife an die Variable Gleis übergeben wurde. Beim Programmieren muss man sehr genau auf das Verhalten solcher Methoden achten. Man muss zum Beispiel sehen, dass der Anfangs- und der Schlusswert beide enthalten sind. Die Schleife wird also mit den Zahlen 1 bis 10 inklusive durchgeführt. Muss man so etwas wissen? Nein! Muss man so etwas lernen? Auswendig können? Nein, auch nicht. Das ginge gar nicht. Es gibt zu viele Unterschiede, die man kennen müsste. Wenn man programmiert, dann muss man ein Bewusstsein für diese Fragen entwickeln. Dann muss man kurz innehalten und denken: "for ... do könnte sich so oder so verhalten. Es könnte inklusive oder exklusive zählen. Ich habe keine Ahnung, wie das bei Lua ist." Und dann probiert man es kurz aus. Wie einfach man solche Dinge testen kann, seht ihr im obigen Beispiel. Hier ist ein weiteres Beispiel: Was passiert, wenn man den Wert der Variablen innerhalb der Schleife ändert? Was bedeutet das für den nächsten Durchlauf? Keine Ahnung! Also probiere ich, was passiert: for Gleis = 1, 10, 1 do Gleis = Gleis + 2 print("Ich prüfe, ob Gleis "..Gleis.." besetzt ist.") end Werden jetzt Zahlen übersprungen? Oder werden alle Zahlen um 2 erhöht und dann ausgegeben? Das muss man nicht auswendig wissen. Das ist auch solange uninteressant, bis man einen Grund hat, den Wert der Variablen innerhalb der Scheife zu ändern. Aber dann will man es zuverlässig wissen. Also muss man sich jetzt bewusst machen, dass es zwei mögliche Verhaltensweisen gibt. Und man muss ausprobieren, welches von beiden das tatsächliche Verhalten ist. Programmieren ist nichts, was man auswendig lernt und dann nach Schema abspult. Programmieren ist eine sehr kreative Beschäftigung. Ständig muss man sich etwas einfallen lassen. Und immer mit der Neugier eines Detektivs betrachten, was genau passiert. Deshalb kann man Programmieren auch nur lernen, indem man sich aktiv damit beschäftigt.
-
Besteht Interesse an einem Lua-Tutorial?
Goetz antwortete auf Goetzs Thema in Allgemeine Diskussionen
Das werde ich gerne tun. Vielen Dank für deine motivierenden Worte -
Im gewählten Beispiel sind natürlich beide gezeigten Methoden Blödsinn, weil man einfach den Wert direkt in den Text einbauen würde. Ich hoffe, dass niemand das missversteht. Ich wollte nur anhand möglichst simpler Beispiele die beiden Konstrukte vorstellen.
-
Beides geht. mit if … elseif … elseif … else bekommt man ein Konstrukt, dass sich genau wie case verhält. Und es ist auch nicht größer als eine case Liste. Die Schreibweise unterscheidet sich nur ein wenig. Bedingung = math.random(1,6) if Bedingung == 1 then print("Du hast eine 1 gewürfelt") elseif Bedingung == 2 then print("Du hast eine 2 gewürfelt") elseif Bedingung == 3 then print("Du hast eine 3 gewürfelt") elseif Bedingung == 4 then print("Du hast eine 4 gewürfelt") elseif Bedingung == 5 then print("Du hast eine 5 gewürfelt") else print("Du hast eine 6 gewürfelt") end Mit Tabellen bekommt man etwas viel besseres. Wenn man nämlich die einzelnen Fälle als Tabellenindex verwendet, dann kann man den vorliegenden Fall benutzen, um direkt dieses Tabellenelement anzuspringen. Man muss nicht vorher - wie bei case oder if … elseif - mehrere andere Fälle überspringen, bis man in der Aufzählung den passenden Fall gefunden hat. Und jedes Tabellenelement kann in Lua eine Funktion sein! Ausgaben = { [1] = function() print("Du hast eine 1 gewürfelt") end, [2] = function() print("Du hast eine 2 gewürfelt") end, [3] = function() print("Du hast eine 3 gewürfelt") end, [4] = function() print("Du hast eine 4 gewürfelt") end, [5] = function() print("Du hast eine 5 gewürfelt") end, [6] = function() print("Du hast eine 6 gewürfelt") end } Bedingung = math.random(1,6) Ausgaben[Bedingung]()
-
Besteht Interesse an einem Lua-Tutorial?
Goetz antwortete auf Goetzs Thema in Allgemeine Diskussionen
Kurze Meldung zum laufenden Lua Tutorial: Ich brauche bitte eine kurze Pause, bevor ich die nächsten Kapitel posten kann. Im Übrigen freue ich mich ganz enorm, dass das Interesse so konstant ist. Die letzten Postings in der Serie wurden ebenso oft abgerufen wie die ersten. Teils sogar häufiger (vermutlich, weil der Eine oder Andere noch einmal etwas nachlesen will.) Insgesamt wird das trotzdem kein vollständiges Lua-Tutorial werden. Das würde meines Erachtens den Rahmen sprengen. Ich wollte euch gerne anhand von Beispielen an die Sprache sowie das Programmieren im Allgemeinen heranführen. Ich möchte euch die Systematik zeigen, die alledem zugrunde liegt. Und dazu habe ich noch ein paar weitere Kapitel geplant, für die ich aber etwas Zeit benötige. -
But of course. A condition leads to a branch in the execution of code. Therefor, one or the other action happens right after the condition has been evaluated. Just like a car would show up at either side of an intersection at the same time after passing the fork in the road..
-
Tabellen - Teil 6 Für den Aufruf von Funktionen, die in einer Tabelle stehen, kennt Lua noch einen besonderen Trick. Und der macht das Anlegen von Funktionen innerhalb von Tabellen erst sinnvoll. Man kann die Adresse der Tabelle beim Aufruf der Funktion als Argument mitgeben. Damit ist es selbst bei tief gestaffelten Tabellen ein Leichtes, direkt auf Elemente der Tabelle zuzugreifen, in der auch die Funktion steht. Ich möchte etwas weiter ausholen, um die Zusammenhänge möglichst durchschaubar zu machen. Als Beispiel soll noch einmal die Zugtabelle aus dem vorletzten Kapitel dienen: Zuege = { { Name = "Gueterzug_1", Geschwindigkeit = 80, Typ = "Gueter", Lok = { Name = "V200", Typ = "Diesel", Kupplung_vorne = false, Kupplung_hinten = true }, Waggons = { { Name = "G10", Typ = "Kasten", Kupplung_vorne = true, Kupplung_hinten = true }, { Name = "Kbs", Typ = "Rungen", Kupplung_vorne = true, Kupplung_hinten = true }, { Name = "OVP", Typ = "Tank", Kupplung_vorne = true, Kupplung_hinten = true }, { Name = "G10", Typ = "Kasten", Kupplung_vorne = true, Kupplung_hinten = false }, } } } Zuege enthält die Adresse der ganzen Tabelle Zuege[1] enthält die Adresse der ersten Untertabelle (in der alle Daten des ersten Zuges stehen) Zuege[1].Waggons enthält die Adresse der Untertabelle mit allen Waggons in der Untertabelle 1 der Tabelle Zuege. Zuege[1].Waggons[1] enthält die Untertabelle mit den Details des ersten Waggons innerhalb der Untertabelle aller Waggons innerhalb … Solche Adressen kann man an eine neue Variable übergeben: Dings = Zuege[1].Waggons[1] Das ist dann sinnvoll, wenn man anschließend diese Adresse mehrfach benötigt. Weil es die Schreibweise der Adressen innerhalb dieser Untertabelle jetzt vereinfacht Dings.Name Dings.Typ Dings.Kupplung_vorne Selbstverständlich wählt man im Ernstfall einen sinnvolleren Namen als „Dings“. Nun stellt euch bitte vor, dass in der Untertabelle eines Waggons eine Funktion enthalten ist, die etwas mit den Daten dieses Waggons tut. Die zum Beispiel den Namen und den Typ des Waggons ausgibt (um die Geschichte jetzt nicht ausufern zu lassen.) Dann würde man beim Aufruf der Funktion die gesamte Adresse innerhalb der Tabelle mit angeben. Zuege[1].Waggons[1].Ausgabe() Jetzt könnte man die Adresse außerdem als Argument mitgeben: Zuege[1].Waggons[1].Ausgabe( Zuege[1].Waggons[1] ) Für dieses Argument legt man in der Funktionsdefinition eine Variable an. Ein gebräuchlicher Name für diese Variable ist „self“. Aber jeder andere Name funktioniert genauso gut. Zuege[1].Waggons[1].Ausgabe = function(self) print("Mein Name ist "..self.Name.." und ich bin vom Typ "..self.Typ) end Das ist ungemein praktisch, weil es die Adressierung der Daten innerhalb dieser Tabelle so bequem macht. Die komplette Adresse der Untertabelle wird an die lokale Variable self übergeben. Also muss man innerhalb der Funktion nur noch self schreiben, wenn man Zuege[1].Waggons[1] meint Und das ist noch nicht alles. Ich kann die identische Funktion für alle Waggons benutzen, weil ich nur die jeweils passende Adresse als Argument an self übergeben muss. Und deshalb hat man in Lua die Schreibweise für den Funktionsaufruf vereinfacht. Programmierer mögen es nämlich nicht, wenn sie etwas doppelt schreiben müssen. Weil es das Risiko vergrößert, sich dabei zu verschreiben. In Lua kann man die Adresse an einer Stelle mit einem Doppelpunkt anstelle des einfachen Punkts aufteilen. Zuege[1].Waggons[1]:Ausgabe() Dann wird automatisch alles, was vor dem Doppelpunkt steht, als erstes Argument übergeben. Werte, die man in die Klammern schreibt, sind damit die Argumente 2 bis … Der Grund, warum ich diesen relativ schweren Stoff in einem Einsteiger-Tutorial anspreche, ist folgender: Die einzelnen Objekte im MBS werden mit V5 fertige Lua Funktionen enthalten. Und der Aufruf dieser Funktionen wird dem folgenden Muster entsprechen: Objektname:Funktionsname(Argumente) Das Objekt hat eigene Daten. Und diese wird die Funktion des Objekts verwenden. Wenn die Funktion ein Signal umstellen soll, dann wird sie dafür die Achsen der Signalflügel ansprechen. Wenn man an einem Kran einen Haken anheben kann, dann muss die Funktion dafür ebenfalls die entsprechenden Parameter des Krans ansprechen und verändern. Wie das im Einzelnen passiert, ist für uns unwichtig. Die Funktion ist in V5 ja fertig definiert. Die Definition müssen wir nicht selbst entwerfen. Aber der Funktionsaufruf wird diesen Gesetzmäßigkeiten folgen. Wir schreiben möglicherweise so etwas wie: Kran:Haken(0) um den Haken in Nullstellung zu bringen. Wie das konkret aussieht, weiß ich auch erst wenn der Beta-Test der V5 beginnt. Mir geht es jetzt ausschließlich darum, dass ihr anhand meiner generischen Beispiele das zugrundeliegende Prinzip durchschaut. Weil dann verständlicher wird, warum was in welcher Reihenfolge im Befehl stehen muss und was es mit dem Doppelpunkt im Funktionsaufruf auf sich hat. Darüber hinaus könnt ihr euch dieses Wissen natürlich auch in eurem eigenen Programmcode zunutze machen. Denn alles, was man rund um den Eisenbahnverkehr steuert, lässt sich wunderbar tabellarisch verwalten. Es lohnt sich wirklich, die Lua-Tabellen zu durchschauen und den Umgang damit zu üben. Weil man tabellarisch später auch sehr komplexe Situationen gut und überschaubar in den Griff bekommt.
-
Tabellen - Teil 5 Ihr habt nun gesehen, dass man in einer Tabelle Zahlen, Strings, Booleans und Tabellen speichern kann. Und das letzte Beispiel hat euch vielleicht gezeigt, warum das praktisch ist. Es geht bei Tabellen nur darum, eine Ansammlung von Daten vernünftig zu organisieren. Das ist ungefähr so, als würde man alles, was zu einem bestimmten Waggon gehört, in eine Schachtel packen: Seinen Namen, die Beschriftungen, die Länge, den Status der beiden Kupplungen, die Position beweglicher Teile etc. Und dann einen Zug bilden, indem man eine Lok-Schachtel und mehrere Waggon-Schachteln hintereinander in eine große Zug-Schachtel steckt. Das hat alles nur organisatorische Bedeutung. @wopitir schrieb mir gestern eine PN, in der er die ganze Tabellenstruktur mit den Ordnern und Unterordnern bei Windows verglich. Und das trifft es ganz gut. Aber Lua Tabellen können noch mehr. Man kann in einer Tabelle auch eine Funktion speichern. Warum auch nicht? Man speichert in Lua ja eigentlich nur die Adressen, unter denen etwas liegt. Ob an dieser Speicheradresse eine Zahl, ein Text oder eine Funktionsdefinition zu finden ist, macht technisch also gar keinen Unterschied. Vorweg muss ich eine alternative Schreibweise für die Definition einer Funktion vorstellen. Diese Variante kennt ihr schon: function Beispiel() print("Ich bin eine Beispielfunktion") end Diese Schreibweise macht dasselbe: Beispiel = function() print("Ich bin eine Beispielfunktion") end Der einzige Unterschied ist die Reihenfolge, in der etwas passiert. Im zweiten Beispiel wird zuerst eine namenlose Funktion angelegt und dann die Adresse dieser Funktion an den Namen Beispiel übergeben. Das Resultat ist aber dasselbe wie bei der ersten Schreibweise. Die zweite Schreibweise eignet sich, um die Adresse einer Funktion an einen Tabellenplatz zu übergeben: Bands = { Beatles = {"John", "Paul", "George", "Ringo"}, Stones = {"Mick", "Keith", "Brian", "Ian", "Bill", "Charlie"}, Who = {"Pete", "Roger", "John", "Keith"}, Anzahl = function(Bandname) print("Die Band "..Bandname.." hat "..#Bands[Bandname].." Mitglieder") end, Erster = function(Bandname) print("Das erste Bandmitglied der "..Bandname.." heißt: "..Bands[Bandname][1]) end } Bands.Anzahl("Stones") Bands.Erster("Who") Beachtet bitte, dass innerhalb der Funktion keine Kommas am Zeilenende stehen dürfen. Denn der gesamte Funktionsblock bildet ein Element. Am Ende der Funktionsdefinition muss hingegen zwingend ein Komma stehen, falls weitere Tabellenelemente folgen. Deshalb steht im obigen Beispiel ein Komma hinter dem end, welches die Definition von Anzahl abschließt. Im MBS sind die Modelle Objekte. Und diese Objekte werden, wenn ich Neo richtig verstanden habe, in V5 eigene Funktionen bekommen, welche man per Lua aufrufen kann. Wie ihr oben seht, ist der Aufruf solcher Funktionen sehr einfach. Aber es hilft wenn man eine Vorstellung davon hat, wie die Daten dazu tabellarisch organisiert sind.
-
Tabellen - Teil 4 Eine Lua Tabelle kann Elemente vom Typ 'table' enthalten Beispiel = { {1, 2, 3} , {63, 127, 255} , {true, true, false} } Das Ergebnis hat Ähnlichkeit mit mehrdimensionalen Tabellen. Die 63 im obigen Beispiel steht an Position Beispiel[2][1], nämlich im zweiten Block an erster Stelle. Der Vergleich mit landläufigen Tabellen ist aber irreführend. Hilfreicher ist, die Lua-Tabellen einfach als Datensammlung zu verstehen. Sie haben in mancher Hinsicht mehr Ähnlichkeit mit Objekten als mit Tabellen. Deshalb müssen Lua-Tabellen nicht dimensioniert werden. Das heißt, dass man nicht (wie in anderen Sprachen) vorher die Anzahl der Zellen festlegen muss. Bands = { Beatles = {"John", "Paul", "George", "Ringo"}, Stones = {"Mick", "Keith", "Brian", "Ian", "Bill", "Charlie"}, Who = {"Pete", "Roger", "John", "Keith"} } print(Bands.Stones[2]) Die Tabelle Bands enthält 3 Elemente, deren Index jeweils ein String ist. Nämlich der Bandname. Jedes der drei Elemente ist ebenfalls eine Tabelle. Die Elemente dieser Untertabellen sind einfache Aufzählungen. Der Index jedes Elements ist also eine Nummer, beginnend mit der Zahl 1 für das erste Element. Beachtet bitte das Komma hinter jeder Untertabelle. Dieses Komma ist erforderlich, um die Elemente Beatles, Stones und Who voneinander zu trennen. Die Zeilenumbrüche und Einrückungen in der Tabelle sind optional und dienen nur der Lesbarkeit. Man könnte ebenso gut den gesamten Tabelleninhalt in eine lange Zeile schreiben. Oder umgekehrt auf noch mehr Zeilen verteilen: Bands = { Beatles = { "John", "Paul", "George", "Ringo" }, Stones = { "Mick", "Keith", "Brian", "Ian", "Bill", "Charlie" }, Who = { "Pete", "Roger", "John", "Keith" } } Das ist reine Geschmacksache. Die Trennung der Elemente entsteht allein durch die Kommas und die geschweiften Klammern. Aber aus Erfahrung kann ich empfehlen, Programmzeilen mit mehr als 80 Zeichen Länge zu vermeiden. Im Übrigen werdet ihr im MBS keine großen Tabellen am Stück erstellen. Dazu besteht kein Grund. Diese Tabellen entstehen auf andere Weise. Aber es wird euch helfen, wenn ihr versteht wie Tabellen in Lua aufgebaut sind. Weil ihr später an einzelne Daten aus solchen Tabellen drankommen wollt. Weil ihr zum Beispiel von einem Zug den Namen des dritten Waggons benötigt: Zuege = { { Name = "Gueterzug_1", Geschwindigkeit = 80, Typ = "Gueter", Lok = { Name = "V200", Typ = "Diesel", Kupplung_vorne = false, Kupplung_hinten = true }, Waggons = { { Name = "G10", Typ = "Kasten", Kupplung_vorne = true, Kupplung_hinten = true }, { Name = "Kbs", Typ = "Rungen", Kupplung_vorne = true, Kupplung_hinten = true }, { Name = "OVP", Typ = "Tank", Kupplung_vorne = true, Kupplung_hinten = true }, { Name = "G10", Typ = "Kasten", Kupplung_vorne = true, Kupplung_hinten = false }, } } } print(Zuege[1].Waggons[3].Name)
-
Tabellen - Teil 3 Der Index eines Tabellenelements darf in Lua auch ein String sein. Man kann die Zellen also mit Namen versehen: Farbe = {} Farbe["Rot_Anteil"] = 128 Farbe["Gruen_Anteil"] = 64 Farbe["Blau_Anteil"] = 0 Die Anführungszeichen um den Namen sind erforderlich, damit Lua an dieser Stelle Zellennamen von Variablen unterscheiden kann. In einer vereinfachten Schreibweise hängt man den Index nach einem Punkt an den Tabellennamen. Bei dieser Schreibweise dürfen keine Anführungszeichen verwendet werden. Farbe = {} Farbe.Rot_Anteil = 128 Farbe.Gruen_Anteil = 64 Farbe.Blau_Anteil = 0 Das ist gut lesbar und bequemer zu schreiben. In der Bedeutung ist es mit der ersten Schreibweise identisch. Ebenso kann man namentliche Indexe gleich bei der Initialisierung verwenden: Farbe = {["Rot_Anteil"] = 128, ["Gruen_Anteil"] = 64, ["Blau_Anteil"] = 0} oder in der vereinfachten Schreibweise: Farbe = {Rot_Anteil = 128, Gruen_Anteil = 64, Blau_Anteil = 0} Bitte beachten: Diese vereinfachten Schreibweisen sind ausschließlich für Strings geeignet. Eine Nummer als Zellenindex muss in eckigen Klammern stehen. Ein Variablename ebenfalls. Nummeriete und namentlich bezeichnete Zellen dürfen in derselben Tabelle vorkommen: Band = {"John", "Paul", "George", "Ringo", Name = "Beatles"} print("Die "..Band.Name.." sind: "..Band[1]..", "..Band[2]..", "..Band[3].." und "..Band[4]) Zur Erinnerung: Mit einem # vor dem Tabellennamen bekommt man den Index vor der ersten Lücke im durchnummerierten Teil einer Tabelle. Im vorliegenden Beispiel liefert #Band also den Wert 4. Die Zelle "Name" wird nicht mitgezählt. Die Funktionen table.insert() und table.remove() verlangen als Index eine Nummer. Sie können nicht auf namentlich indizierte Zellen angewendet werden. Möchte man eine Zelle löschen, dann weist man ihr den Wert nil zu. Das funktioniert mit jedem Index. Ist der Index eine Zahl, dann werden die nachfolgenden Zellen nicht verschoben und es entsteht eine Lücke in der Nummerierung.
-
Tabellen - Teil 2 Möchte man wissen, wieviele Elemente sich in einer Tabelle befinden, dann kann man diese Zahl mit einem # vor dem Namen der Tabelle bekommen: Beispiel = {"Eins", "Zwei", "Drei"} Anzahl = #Beispiel Man kann in Lua eine leere Tabelle anlegen Beispiel = {} Und anschließend Elemente hinzufügen Beispiel[1] = "Eins" Beispiel[2] = "Zwei" Bei dieser Methode werden keine Elemente verschoben. Eventuell vorhandene Elemente werden überschrieben. Wenn man den Index angibt, unter dem Elemente gespeichert werden sollen, sind Lücken in der Liste möglich Beispiel[5] = "Fünf" #Beispiel gibt in diesem Fall den Index vor der ersten Lücke zurück. Das wäre im vorliegenden Beispiel eine 2 Im folgenden Beispiel wäre es eine 5, obwohl die Elemente 1 und 2 nicht existieren und das letzte Element den Index 7 hat. Beispiel = {} Beispiel[3] = "Drei" Beispiel[4] = "Vier" Beispiel[5] = "Fünf" Beispiel[7] = "Sieben" print(#Beispiel) Man kann Tabellen wahlweise nutzen um sortierte, durchnummerierte Listen zu erstellen oder um Elemente an bestimmte Nummern wie z.B. IDs zu knüpfen. Da Lua beides im selben Typ 'table' verwaltet, muss man selbst darauf achten, die beiden Fälle richtig zu unterscheiden. Für Änderungen an durchnummerierten Listen gibt es unter anderem die Funktionen table.insert(Tabelle, Index, Wert) und table.remove(Tabelle, Index) Lässt man bei table.insert() den Index weg (zwei Parameter statt drei), hängt es den neuen Wert dort an, wo in der Liste die erste Lücke gefunden wird. Nachfolgende Elemente verschieben sich nicht! Damit fungiert es als append, welches Lua nicht als eigenständige Funktion bietet. Beispiel = {} Beispiel[3] = "Drei" Beispiel[4] = "Vier" Beispiel[5] = "Fünf" Beispiel[9] = "Neun" table.insert(Beispiel, "Sechs") for i = 1, 10 do print(i.." - ", Beispiel[i]) end Die Funktion table.remove() liefert den entfernten Wert als Ergebnis zurück. Beispiel = {"Eins", "Zwei", "Drei", "Vier", "Fünf", "Sechs"} for i = #Beispiel-1, 1, -1 do table.insert(Beispiel, table.remove(Beispiel, i)) end for i = 1, #Beispiel do print(i.." - "..Beispiel[i]) end
-
Tabellen - Teil 1 Variablen bieten die Möglichkeit, einen Wert unter einem Namen abzulegen. Oftmals ist es aber sinnvoll, mehrere Werte unter einem einzelnen Namen zusammenzufassen, weil sie in irgendeiner Weise zusammengehören. Koordinaten zum Beispiel. Oder Farbwerte. Oder Personendaten. Oder alle Parameter einer Lok (Name, Geschwindigkeit, Ort …) Für diesen Zweck bietet Lua Tabellen. Und in Lua gibt es nur Tabellen. Andere Programmiersprachen kennen außerdem Listen, Dictionaries, Tupfes und mehr. Lua fasst das alles in einem Typ 'table' zusammen. Tabellen werden mit geschweiften Klammern erzeugt. Koordinaten = {0, 0, 0} und die Elemente einer Tabelle werden durch ein Komma getrennt. Die geschweiften Klammern findet man auf einer normalen, deutschen PC-Tastatur mit AltGr 7 und AltGr 0 Die einzelnen Elemente einer Tabelle sind indiziert. Das bedeutet, dass sie durchnummeriert sind. Und dass man sie über diese Nummer ansprechen kann. Den Index eines Tabellenelements schreibt man bei Lua in eckige Klammern Koordinaten[1] Die eckigen Klammern findet man auf einer normalen, deutschen PC-Tastatur mit AltGr 8 und AltGr 9 Eine Lua Tabelle darf Elemente unterschiedlichen Typs enthalten Person = {"Max", "Mustermann", 182, 3.5, true} Diese Tabelle enthält zwei Strings, eine Ganzzahl, eine Fließkommazahl und ein Boolean. Das erste Element einer Tabelle hat den Index 1 Person[1] == "Max" Darin unterscheidet sich Lua von allen anderen Programmiersprachen! Für den Einsteiger ist diese Nummerierung sehr angenehm, weil sie seinen Erwartungen entspricht. Für routinierte Programmierer ist sie eine Falle, weil sie den bisherigen Gewohnheiten widerspricht. Für die Lesbarkeit darf man beliebig viele Leerzeichen einfügen. Farbe = {0, 32, 128} und Farbe = { 0, 32, 128} sind identisch. Ebenso darf man Zeilenumbrüche einfügen um die Lesbarkeit zu verbessern Texte = { "Eine Lok steht auf Gleis 1", "Der Güterzug wurde abgefertigt", "Der Weg für den Schnellzug ist frei", "Der Schattenbahnhof ist voll!" } Das Komma als Trennzeichen zwischen den Elementen ist auch bei mehrzeiliger Schreibweise zwingend erforderlich. Hinter dem letzten Element ist ein Komma optional. Man darf es schreiben, aber Lua verlangt es nicht.
-
Besteht Interesse an einem Lua-Tutorial?
Goetz antwortete auf Goetzs Thema in Allgemeine Diskussionen
Ja. Das Schlüsselwort dafür heißt break Wenn du einen for-Schleifenzähler über die Schleife hinaus noch benötigst, musst du ihn - genau wie du vermutest - übergeben. Was du im while ... do Konstrukt stehen hast, ist kein Zähler, sondern eine Bedingung. Ob du diese Bedingung innerhalb der While Schleife durch Zählen oder anderweitig veränderst, macht keinen Unterschied. Die Variable wird nicht in der Bedingung erzeugt und ist nicht automatisch local. -
Variablen für Fortgeschrittene Lua Variablen können ohne Deklaration Daten jeden Typs speichern. Und man kann auch beim Überschreiben von Werten den Typ ändern. Ich kann beispielsweise eine Zahl mit einem String überschreiben, ohne dass Lua meckert. Das geht deshalb, weil in den Variablen selbst gar kein Wert steht, sondern nur eine Speicheradresse. Die Variable speichert nur, wo etwas steht aber nicht, was dort steht. Und Adressen sind immer gleich lang. Egal, ob sie auf eine Zahl, einen String oder etwas anderes zeigen. Variablen sind in Lua immer global definiert. Man muss ihnen bei der Deklaration das Schlüsselwort local voran stellen, um lokale Variablen zu erzeugen. function Test_a() a = 1 return a end function Test_b() local b = 1 return b end if Test_a() == a then print("a ist eine globale Variable") else print("a ist eine lokale Variable") end if Test_b() == b then print("b ist eine globale Variable") else print("b ist eine lokale Variable") end Funktionsargumente und Schleifenzähler sind automatisch lokale Variablen Lua erlaubt multiple Zuweisungen a, b = 2, 3 Damit lassen sich auch Werte tauschen: a, b = b, a Wenn man mehrere Variablen zugleich deklariert, dann kann man allen zusammen einmal das Schlüsselwort local voranstellen local a, b, c = 1, 2, 3
-
Variablen im 3D-Modellbahn Studio Im MBS gibt es Daten, die man als User gerne auslesen und verwenden möchte. Zum Beispiel die Stellung eines Signals. Oder die Geschwindigkeit einer Lok. Oder der Name des Fahrzeugs auf einem Gleisstück. Damit man in der EV an diese Daten kommt, haben die Speicherplätze für diese Daten eigene Namen. Das sind also Variablen, deren Name schon im MBS festgelegt wurde. Wenn man davon absieht, dass die Namen dieser Speicherplätze schon festliegen und die Werte in diesen Speicherplätzen vom MBS stammen, unterscheiden sich diese Variablen nicht von solchen, die man selbst deklariert. Es sind Speicherplätze, die einen Namen bekommen haben, damit man sie im Programmcode oder in der EV verwenden kann. Ein Teil der Schwierigkeiten, welche manche User mit Variablen haben, stammen möglicherweise von den Namen dieser Variablen. Man muss die richtigen Namen kennen. Und das sind vielleicht nicht die Namen, welche man selbst ausgesucht hätte. An dieser Tatsache wird auch Lua nichts ändern. Wenn eine Person „Peter“ heißt, dann muss ich sie mit „Peter“ ansprechen. Auch, wenn ich finde, dass der Name „Klaus“ viel besser passen würde. Peter fühlt sich nur angesprochen, wenn ich ihn mit seinem Namen anrede. Welche MBS-Variablen es in V5 geben wird, weiß ich nicht. Aber sie werden eine wichtige Rolle spielen, weil man auch mit Lua die MBS Objekte steuern will. Das geht nur, wenn ich die Namen der einzelnen Objektparameter kenne. In Lua Skripten werden sowohl Variablen vorkommen, die vom MBS stammen als auch solche, die man selbst definiert. Und die müsst ihr unterscheiden können. Ihr müsst verstehen, wann ihr euch an vorgegebene Namen halten müsst und wann ihr eigene Namen definiert. Das ist nicht schwer. Es muss einem nur bewusst sein, dass es diesen Unterschied gibt.
-
Variablen Bislang habe ich den Variablen kein eigenes Kapitel gewidmet. Normalerweise fängt man ein Tutorial damit an, dass man Variablen erklärt. Aber ich weiß, dass manche hier Abneigungen verspüren, wenn dieses Thema aufkommt. Und man müsste Variablen abstrakt und ohne passenden Zusammenhang erklären, wenn man ein Tutorial mit diesem Thema beginnt. Deshalb hatte ich mich für eine andere Reihenfolge entschieden. In den ersten Übungen habt ihr Variablen schon benutzt. Ihr habt sie kennengelernt, ohne dass ich sie im Detail erklärt habe. Und ihr habt schon den Zusammenhang gesehen, in dem man Variablen einsetzt. Variablen sind nichts anderes als Platzhalter. Wenn ich beim Entwurf einer Funktion bestimmte Inhalte noch nicht kenne, dann schreibe ich Platzhalter dorthin, wo später diese Inhalte eingesetzt werden sollen. Und damit ist im Prinzip schon erklärt, was eine Variable ist. Mehr steckt nicht dahinter. Man kann Variablen einen beliebigen Namen geben. Der darf aus einem einzelnen Buchstaben bestehen oder ein ganzes Wort sein. Aber der Name darf nur aus Buchstaben, Ziffern und dem Unterstrich bestehen. Und er darf nicht mit einer Ziffer beginnen. (Ich hatte das in einem früheren Kapitel schon einmal geschrieben. Aber es ist so wichtig, dass ich es hier noch einmal wiederholen möchte.) Vermeidet spezielle Buchstaben wie z.B. Umlaute oder das ß. Sicher kennt ihr von E-Mails das Problem, dass an Stelle solcher Buchstaben manchmal kryptische Zeichenfolgen auftauchen? Dasselbe könnte euch auch im Programmcode passieren und dann für Probleme sorgen.
-
Lösungsbeispiele „Differenz21“ Beginnen möchte ich wieder mit dem Lösungsweg, der die Aufgabe „buchstäblich“ umsetzt: function Differenz21(n) Ergebnis = math.abs(21 - n) if n > 21 then Ergebnis = Ergebnis * 2 end return Ergebnis end Zuerst wird die Differenz von 21 und n gebildet und der Betrag dieser Differenz in einer Variablen Ergebnis gespeichert. Dann wird geprüft, ob n größer als 21 ist. Falls ja, dann wird der Wert in Ergebnis verdoppelt und wieder in Ergebnis gespeichert. Zuletzt wird der Wert von Ergebnis ausgegeben. Es wäre sauberer, Ergebnis als eine lokale Variable anzulegen. Aber mit der globalen Variable funktioniert das Skript ebenso gut. Drum habe ich mehr Wert auf ein leicht lesbares Beispiel gelegt. Unter anderem sollte dieses Beispiel dazu dienen, die math.abs() Funktion vorzustellen. Die ist praktisch und mit einem einfachen Beispiel, wie dem hier gezeigten, leicht zu verstehen. Aber für die gestellte Aufgabe kommt man auch ohne diese Funktion aus. function Differenz21(n) Ergebnis = 21 - n if n > 21 then Ergebnis = Ergebnis * -2 end return Ergebnis end Das Ergebnis von 21 - n kann ja nur dann negativ sein, wenn n größer als 21 ist. Und wenn man in diesem Fall das Ergebnis sowieso verdoppeln muss, dann kann man es auch mit -2 multiplizieren, um aus dem negativen einen positiven Wert zu machen. Damit entfällt die Notwendigkeit, den Betrag der Differenz zu bilden. Außerdem ist eine Überlegung wert, ob man den ursprünglichen Wert von n im weiteren Verlauf behalten muss. Falls nicht, dann kann man die Variable n selbst verändern und wieder auszugeben. function Differenz21(n) n = 21 - n if n < 0 then n = n * -2 end return n end Man spart so eine zusätzliche Variable ein. Und n ist, wie alle Funktionsargumente, eine lokale Variable. Es ist riskant, den Wert der ursprünglichen Variable zu ändern. Schnell übersieht man dabei, dass man den ursprünglichen Wert später noch benötigt. Im vorliegenden Fall für den Vergleich, ob n größer als 21 ist. Das letzte Beispiel funktioniert nur, weil ich diesen Vergleich durch einen anderen ersetzt habe. Denn wenn n größer als 21 war, dann ist das Ergebnis von 21 - n kleiner als 0. Alternativ kann man auch erst prüfen, ob n größer als 21 ist und dann entsprechend verzweigen function Differenz21(n) if n > 21 then return (n - 21) * 2 end return 21 - n end Durch den Tausch der beiden Werte in der Subtraktion entfällt in der dritten Zeile das negative Vorzeichen vor dem Multiplikator. Der Einzeiler für Fortgeschrittene kann so aussehen: function Differenz21(n) return n > 21 and (n - 21) * 2 or 21 - n end Ich hoffe, ihr hattet Spaß an diesen Knobeleien? In den nächsten Kapiteln will ich wieder ein paar Grundlagen zu Lua erklären.
-
Differenz21 In dieser Aufgabe hat die zu definierende Funktion nur einen Parameter vom Typ "number". Ich gebe der Variable den Namen n. Die Funktion soll den absoluten Wert der Differenz zwischen 21 und n zurückgeben. Und falls n größer als 21 ist, soll dieser Wert verdoppelt werden. Als absoluten Wert bezeichnet man die positive Version einer Zahl. Ein anderer Ausdruck für den absoluten Wert ist "Betrag". Der Betrag von 5 ist 5 Der Betrag von -5 ist ebenfalls 5 Beispiele: Für n = 1 muss die Funktion den Wert 20 ausgeben Für n = 25 muss die Funktion den Wert 8 (das doppelte des Betrags von -4) ausgeben function Differenz21(n) end Lua bringt eine vordefinierte Funktion mit, die den Betrag einer Zahl bildet. Diese Funktion heißt math.abs() und ist eine von mehreren Funktionen aus der "math" Bibliothek. Deshalb hat sie diesen zweiteiligen Namen mit dem Punkt dazwischen. Dazu erkläre ich in späteren Kapiteln mehr. Ein Skript mit Prüfroutine hänge ich wieder an dieses Posting an. Viel Spaß beim Ausprobieren und Studieren. Differenz21.zip
-
für Fortgeschrittene In Lua sind Variablen grundsätzlich global definiert! Mit Ausnahme der Funktionsargumente, die selbstverständlich lokale Variablen sind. Möchte man eine lokale Variable erzeugen, dann muss man dafür in Lua das Schlüsselwort local benutzen. Richtig müsste mein letztes Beispiel aus dem vorherigen Posting also lauten: function Wurf(a, b) local c = a + b if a == b then c = 2 * c end return c end Eine lokale Variable gilt nur innerhalb der Funktion und wird bei Verlassen gelöscht. Die Verwendung lokaler Variablen innerhalb von Funktionsdefinitionen vermindert das Risiko von Namenskonflikten (weil der Namensbereich auf die Funktion und ihre Unterfunktionen beschränkt ist) spart Speicherplatz (weil der Platz für diese Variable nach Verlassen der Funktion frei gegeben wird) beschleunigt das Programm (weil diese kurzlebigen Variablen im Prozessor-nahen Speicher angelegt werden.) Für die letzte Aufgabe gibt es noch einen weiteren, interessanten Lösungsweg. Er macht sich das besondere Verhalten von and und or zunutze, welches ich im Kapitel 2b or not 2b angesprochen hatte. function Wurf(a, b) return a == b and 4 * a or a + b end Wenn a == b wahr ist, dann ist das Ergebnis der and Verknüpfung der zweite Term 4 * a Und weil dieser Term auf jeden Fall wahr ist, wird der Term hinter dem or nicht mehr geprüft, sondern das Resultat der and Verknüpfung ausgegeben. Nämlich 4 * a, ganz wie gewünscht. Ist die erste Bedingung hingegen nicht wahr, dann ist das Ergebnis der and Verknüpfung auch nicht wahr. Und in diesem Fall wird der Term ausgegeben, der hinter dem or steht. Nämlich a + b. Dieser Weg ist kein bisschen schneller als die zuvor gezeigten. Das ist nur eine akademische Spielerei, die aber mächtigen Spaß machen kann und zu mehr Routine führt.
-
Lösungsbeispiele zu "doppelte Summe" Zuerst möchte ich einen Lösungsweg zeigen, den ich häufig sehe. Er setzt den Aufgabentext "buchstäblich" um: function Wurf(Zahl_1, Zahl_2) if Zahl_1 == Zahl_2 then return (Zahl_1 + Zahl_2) * 2 else return Zahl_1 + Zahl_2 end end Wenn die beiden Zahlen gleich sind, dann bilde die Summe, vedopple sie und gib das Ergebnis aus. Andernfalls gib einfach die Summe der beiden Zahlen aus. Beachtet bitte, dass bei der Verdopplung in der dritten Zeile die Summe in Klammern stehen muss. In Lua gilt Punktrechnung vor Strichrechnung. Das heißt, dass ohne die Klammern nur die zweite der beiden Zahlen verdoppelt würde. Hier ist ein Vorschlag zur Vereinfachung des obigen Beispiels: In der zweiten Zeile wird geprüft, ob beide Zahlen gleich sind. Und die dritte Zeile wird nur dann ausgeführt, wenn diese Bedingung erfüllt war. Also ist Zahl_1 + Zahl_2 an dieser Stelle dasselbe wie Zahl_1 + Zahl_1 oder 2 * Zahl_1. Deshalb kann man die Rechenoperation in Zeile 3 vereinfachen. function Wurf(Zahl_1, Zahl_2) if Zahl_1 == Zahl_2 then return 4 * Zahl_1 else return Zahl_1 + Zahl_2 end end Ein weiterer Weg wäre der, dass man zuerst eine der beiden Zahlen ändert, falls beide gleich sind. Und dann die Summe bildet. function Wurf(Zahl_1, Zahl_2) if Zahl_1 == Zahl_2 then Zahl_1 = 3 * Zahl_1 end return Zahl_1 + Zahl_2 end Für das Ergebnis macht dieser unorthodoxe Weg keinen Unterschied. Man kommt damit genauso schnell und sicher zum Ziel wie mit den ersten beiden Wegen. Aber diese Fassung ist schwer nachvollziehbar und deshalb nicht wirklich ratsam. Trotzdem haben mir solche Gedankenexperimente geholfen, Lua und überhaupt das Programmieren besser zu verstehen. Deshalb möchte ich euch gerne dazu animieren, solche verschiedenen Lösungswege durchzuspielen. Mit diesen sehr einfachen Aufgaben, die nur wenige Zeilen Programmcode erfordern, kann man das gut machen. Die folgende Methode ist populär und gut lesbar: function Wurf(Zahl_1, Zahl_2) Ergebnis = Zahl_1 + Zahl_2 if Zahl_1 == Zahl_2 then Ergebnis = 2 * Ergebnis end return Ergebnis end Diesmal wird das Ergebnis in mehreren Schritten gebildet und in einer neuen Variable namens Ergebnis zwischengespeichert. Lua kann diese Variable gleich in der Funktion bilden. Man muss sie nicht erst deklarieren, wie das vor allem bei Compiler-Sprachen erforderlich ist. Man weist der Variablen einfach einen Wert zu und kann sie anschließend verwenden. Selbstverständlich kann diese Variable jeden beliebigen Namen haben und muss nicht zwingend Ergebnis heißen. Und genauso kann man übrigens auch die Namen der Funktionsargumente ändern. Diese Variablen existieren ja nur innerhalb der Funktion. Man kann das letzte Beispiel also auch so schreiben: function Wurf(a, b) c = a + b if a == b then c = 2 * c end return c end Dem einen oder anderen ist diese Schreibweise vielleicht einleuchtender. Weil die Formeln besser zutage treten. Nur den Namen der Funktion solltet ihr bitte beibehalten, wenn ihr mein kleines Testskript verwenden wollt. Denn das ist der Funktionsname, der weiter unten mit verschiedenen Beispielwerten aufgerufen wird.
-
Du findest Lua in sehr vielen Spielen wie z.B. Minecraft oder Factorio. Meines Wissens eignet es sich besonders gut für diese Art der Integration in Programme. Und es ist - wie Python - sehr Einsteiger-freundlich. https://en.wikipedia.org/wiki/List_of_applications_using_Lua#Video_games
-
doppelte Summe Die dritte Aufgabe hat dasselbe Muster wie die vorherigen beiden. Aber die beiden Parameter beim Aufruf sind diesmal keine booleans (true oder false), sondern Zahlen. Die Funktion soll die Summe der beiden Zahlen zurückgeben. Und wenn beide Zahlen gleich sind, dann soll sie die Summe vor der Ausgabe verdoppeln. function Wurf(Zahl_1, Zahl_2) end Ich habe die Funktion "Wurf" genannt, weil die Aufgabe für ein Würfelspiel geeignet wäre. Der Spieler würde per Knopfdruck zwei Zahlen würfeln und die Funktion, welche ihr hier definiert, errechnet dann das Ergebnis. Ein Pasch zählt dabei doppelt. Im Anhang findet ihr wieder ein Skript mit angehängter Testroutine, welches ihr für die Prüfung eurer Lösungsansätze verwenden könnt. Ich freue mich über jeden Lösungsvorschlag, den ihr mir als Nachricht schickt. doppelte Summe.zip
-
Besteht Interesse an einem Lua-Tutorial?
Goetz antwortete auf Goetzs Thema in Allgemeine Diskussionen
Ich glaube, für eine ehrliche Antwort muss sich niemand entschuldigen. Und Lua muss dich nicht erschrecken. Weil es nicht erforderlich ist, dass man das lernt. Die V5 wird dir gewiss auch ganz ohne Lua viel Freude machen. Und du wirst auch ohne Lua Kenntnisse auf nichts verzichten müssen. Lua ist einfach nur eine weitere Möglichkeit der Anlagensteuerung und für diejenigen gedacht, denen diese Methode liegt. Es ist wie ein weiteres Musikinstrument. Man muss nicht alle Instrumente beherrschen, um Musik zu machen. Aber man kann eine größere Auswahl an Musik genießen, wenn alle Musiker viele verschiedene Instrumente zur Auswahl haben.