Lua rennt - ein Einsteiger Tutorial (nur Tutorial)

!!! Please ensure, that your contribution or question is placed into the relevant section !!!
Questions about rolling stock, for example, do not belong in "Questions about the Forum". Following is perhaps the right area where your question will be better looked after:
General questions to EEP , Splines, rolling stock, Structures in EEP, landscape elements, Signalling system and controlling, designers, Europe-wide EEP meetings , Gossip
Your cooperation to keep the forum clear is appreciated.
  • Ich beginne hier mit einem Einsteiger Tutorial in gaaaanz kleinen Schritten.


    Bevor man mit einer Programmier- oder Skriptsprache etwas machen kann, muss man sie verstehen.
    Anders geht es nicht!
    Durchpausen oder beim Nachbarn abschreiben führt zu nichts.



    An alle, die sich jetzt bemüßigt fühlen hier mit Ratschlägen oder cleveren Ideen etwas beizutragen:
    Lasst es bitte.
    Ich habe klare, didaktische Vorstellungen und wenn ich bestimmte Dinge zunächst nur zur Hälfte erkläre, dann habe ich einen sehr guten Grund dafür! Ich erinnere nämlich aus Schulzeiten sehr genau, wie verwirrend es ist, wenn Lehrer und Schlauberger aus der Klasse gleich alle Sonderfälle mit erklären. Das verwirrt und mindert den Lernerfolg.


    Unpassende Beiträge, die hier dennoch gepostet werden, werde ich mit Hilfe der Moderatoren gnadenlos rausschmeißen!


    Vorschläge und Hinweise per PN nehme ich aber sehr gerne an.




    Ich werde nicht damit beginnen einen nützlichen Lua Code für irgendetwas zu präsentieren. Das kommt später.
    Denn bevor das sinnvoll ist, müssen zunächst ein paar Grundlagen klar sein, damit solch ein Code überhaupt lesbar ist.



    Das erste Kapitel behandelt Variablen.


    Hinweis: Den Faden mit Diskussionsbeiträgen findet ihr hier: Lua rennt - ein Einsteiger Tutorial (mit Diskussion)

  • In einem Programm muss man sich oft etwas merken.
    Beispielsweise eine Zahl, die man ausgerechnet hat.
    Um diese Zahl später wieder zu benutzen.


    Für diesen Zweck gibt es Variablen.
    Diese Variablen können einen Namen bekommen, den man selber festlegt.
    Man kann sie zum Beispiel John nennen.
    Oder Paul
    Oder George
    Oder Ringo


    Genauso gut gehen
    Gleis
    Zug
    Besetzt
    oder auch Vergissmeinnicht



    Aber der Name muss immer mit einem Buchstaben beginnen!
    Und er darf nur normale Buchstaben, Ziffern und den Unterstrich enthalten. Sonst nichts!
    Denn alle Sonderzeichen haben in Lua Aufgaben.
    Beispielsweise der Bindestrich. Der wird als Minus benutzt


    Gibt man einer Variablen den Namen John-Paul, dann zieht Lua Paul von John ab.
    Ob bei dieser Subtraktion etwas sinnvolles entsteht ist Lua völlig egal.
    Da steht ein Minus in der Zeile, also wird gerechnet. Punkt!



    Wenn ich am Anfang des Lua Skripts schreibe:
    John = 3
    dann bedeutet das für Lua:
    „Immer, wenn der Götz John schreibt, dann meint er 3“



    Steht also weiter hinten im Skript der Befehl
    EEPSetSwitch(John, 2)
    dann wird die Weiche 3 in die Stellung 2 (also Abzweig) gebracht.


    Apropos:


    Ich kann auch am Anfang des Skripts schreiben:
    Fahrt = 1
    Abzweig = 2


    Dann kann ich später im Skript diese Worte für die Weichenstellungen benutzen und so mein Skript etwas lesbarer machen.


    Ich kann also später schreiben:
    EEPSetSwitch(John, Fahrt)
    und Lua weiß, dass damit gemeint ist:
    Weiche 3 in Stellung 1 bringen.


    Weil EEP sich am Anfang gemerkt hat, dass John 3 bedeutet und Fahrt 1.


    Die meisten Programmierer machen sich solche Mühe nicht. Weil es umständlich und unnötig ist.
    Aber es zeigt, was eine Variable überhaupt ist.


    Weil in großen Programmen viele Variablen benötigt werden und man deshalb viele Namen benötigt, wird jeder Unterschied im Namen als eine andere Variable angesehen


    John ist also eine andere Variable als john oder JOHN oder jOhn


    Ein einzelner Buchstabe reicht als Variablenname.
    Ich kann sie also beispielsweise
    Z
    nennen.


    Und Z ist eine andere Variable als Z_BUE.
    Die zwei haben nichts miteinander zu tun.
    Ich kann mir in Z eine Zahl merken und in Z_BUE eine andere.



    Der Wert, der in einer Variablen gespeichert wird, ist veränderbar.
    Deshalb nennt man diese kleinen Speicherplätze Variable. Weil sie genau das sind: variabel.


    Wenn am Anfang des Skripts stand
    Gleis = 1
    und ich etwas später im Programm schreibe
    Gleis = 2
    dann ist ab jetzt mit Gleis nicht mehr die Zahl 1, sondern 2 gemeint.
    Bis ich den Wert erneut überschreibe.



    Mit Variablen kann ich auch rechnen, als ob es Zahlen wären.
    Ich kann beispielsweise
    Gleis + 1 ausrechnen
    Wenn Gleis den Wert 2 gespeichert hat, dann bedeutet die Rechenaufgabe oben
    2+1
    und das Ergebnis ist 3


    Wenn ich dieses Ergebnis anschließend benutzen will, dann muss ich es wieder in einer Variablen speichern.
    John = Gleis + 1
    heißt nimm den Wert, der in Gleis gespeichert ist, addiere Eins hinzu und speicher das Ergebnis in der Variablen John.


    Ich kann auch schreiben
    Gleis = Gleis + 1
    was bedeutet:
    Nimm den alten Wert, der in Gleis gespeichert war, addiere 1 hinzu und Speicher das Ergebnis wieder in der Variablen Gleis ab.


    Zuletzt noch eine sehr wichtige Information, die ich benahe vergessen hätte:
    In einer Programmier- oder Skriptsprache gibt es viele Befehle.
    Und das sind auch nur Buchstaben.


    Wie kann Lua also unterscheiden, ob ein Befehl gemeint ist oder eine Variable?
    Ganz einfach:
    Sämtliche Befehle, die es in Lua gibt, sind als Name für eine Variable streng verboten!
    Da alle Befehle (meines Wissens) nur aus Kleinbuchstaben bestehen ist man auf der sicheren Seite, wenn der Name einer Variablen mindesten einen Großbuchstaben enthält.



    Damit ist die erste Erläuterung zu Variablen beendet. Es gäbe zwar noch mehr zum Thema zu sagen. Aber das hebe ich mir aus gutem Grunde für später auf.


    Im nächsten Kapitel werde ich Funktionen erläutern.

  • Im Grunde genommen sind Funktionen - Programmierer schauen jetzt bitte mal weg! - auch Variablen.


    Jedenfalls gibt es da einige Gemeinsamkeiten.


    Wie bei einer Variablen vergibt man auch bei einer Funktion den Namen selber.
    Und es gelten die gleichen Regeln:

    • Am Anfang ein Buchstabe
    • Nur normale Buchstaben (also keine Umlaute, kein ß etc.)
    • Ziffern
    • und den Unterstrich
    • keine Sonderzeichen (also kein % oder $ oder ! oder …)
    • kein Leerzeichen


    Eine weitere Gemeinsamkeit ist, dass in einer Funktion etwas gespeichert wird.
    Allerdings mehr als nur eine Zahl.
    Sondern zum Beispiel eine Rechenaufgabe.


    Diese Aufgabe muss ich erst einmal entwerfen und so die Funktion festlegen.


    Damit Lua versteht, dass ich die Aufgabe festlegen will, schreibe ich vor den Funktionsnamen das Wort function und hinter den Funktionsnamen die runden Klammern.


    function ist einer dieser Lua Befehle, die ich zum Ende des vorigen Kapitels kurz angesprochen hatte.
    function bedeutet:
    Merke dir die folgende Aufgabe unter diesem Namen.


    Also beispielsweise:
    function Ausrechnen()
    1 + 2
    end


    Weil in einer Funktion mehrere Aufgaben stecken können muss man Lua zeigen, wo die Aufgabenliste zu Ende ist.
    Dafür ist der Lua Befehl end.



    Später kann ich dann die Funktion einfach mit ihrem Namen aufrufen.


    Wenn ich also an späterer Stelle in mein Skript schreibe:
    Ausrechnen()
    dann rechnet Lua 1 + 2 aus.


    Allerdings habe ich nichts davon, weil in der Funktion oben nicht drin steht, dass Lua sich das Ergebnis merken soll.


    Würde ich stattdessen schreiben
    function Ausrechnen()
    Ergebnis = 1 + 2
    end
    dann hätte ich eine Variable, in der das Ergebnis von 1 + 2 gespeichert würde.



    Allerdings würde der Wert nur so lange in dieser Variablen gespeichert, wie ich mich innerhalb der Funktion befinde. Sobald die Aufgaben in der Funktion erledigt sind, vergisst Lua diese Variable wieder. Der Grund dafür ist, dass man in großen Programmen eine unvorstellbare Anzahl an Variablen benötigt. Und weil in jeder Variablen etwas gespeichert wird, brauchen diese Platz. Davon möchte man nur so viel belegen, wie nötig. Deshalb ist es sinnvoll, manche Variablen zu vergessen, wenn sie ihren Zweck erfüllt haben.


    Manche Variablen braucht man nur innerhalb einer Funktion.
    Weil man sich nur kurz etwas merken muss.
    Deshalb gilt für Variablen die Regel:
    Wenn sie nur innerhalb der Funktion genannt werden, dann vergisst Lua sie, sobald die Funktion erledigt ist.


    Variablen, die gleich zu Anfang des Skripts genannt werden - also noch vor allen Funktionsaufrufen - bleiben so lange erhalten, bis das Programm beendet wird.



    In den paar Zeilen, aus denen jedes EEP Lua Skript zunächst besteht, steckt gleich vorne solch eine Variable.
    Sie hat den Namen I und bekommt zunächst den Wert 0


    I = 0


    Weil diese Variable I schon vor der ersten Funktion genannt wird bleibt sie erhalten, bis das Lua Skript beendet wird.


    Kurz darauf folgt die Festlegung einer Funktion. Sie bekommt den Namen EEPMain().
    Main ist englisch und bedeutet in etwa Hauptsache


    Mit
    function EEPMain()
    wird also das Hauptprogramm festgelegt.


    Und das tut zunächst mal sehr wenig.
    Aber auf eins, was das Hauptprogramm tut, möchte ich hier hinweisen:


    Dort steht nämlich
    I = I + 1
    Und dieses I wurde etwas weiter oben als Variable festgelegt.


    Eben war der Wert für I noch 0. Wenn die Hauptfunktion jetzt durchlaufen wird, dann ändert er sich zu 1
    Beim nächsten Durchlauf wird daraus 2, dann 3, dann 4 und so weiter.



    Wer jetzt keinen Sinn darin erkennt, der hat schon viel verstanden.
    Denn da ist auch keiner.
    Mit diesem Wert, der in I gespeichert wird, passiert ansonsten nämlich nichts.


    Löscht man oben die Zeile
    I = 0
    und in der Funktion die Zeile
    I = I + 1
    dann funktioniert das Skript genauso gut wie vorher.



    Mooooment
    Passiert mit diesem I wirklich gar nichts?
    Doch!
    Da steht nämlich noch die Zeile
    print (I)


    print ist ebenfalls ein Befehl
    Eigentlich ist print das englische Wort für Drucken.
    Aber seit Computer Bildschirme haben und nicht mehr alles auf lange Papierbänder ausdrucken müssen, steht dieser Befehl für „Schreib das in mein Bildschirmfenster“


    Wenn man es ganz genau betrachtet, ist print kein Befehl, sondern eine Funktion, die schon fest definiert ist.
    Das erkennt man an den Klammern.
    In die Klammern schreibt man etwas, das die Funktion für ihre Aufgaben benutzen soll.
    In diesem Fall einen Text
    In den Klammern steht das vertraute I
    Also eine Variable
    Die Funktion print() schaut deshalb nach, was in der Variablen I gespeichert ist und schreibt es in das Ausgabefenster.


    Wozu ist das gut?


    Sehr einfach:
    Lua gibt auf diese Weise im Ausgabefenster ein Lebenszeichen von sich.


    Wenn man an den Zeilen, die von Anbeginn im Lua Skript stehen, nichts ändert und das Ausgabefenster aktiviert hat, dann kann man beobachten, dass in diesem Fenster fleißig gezählt wird.


    Man sieht also, dass die Funktion EEPMain() immer wieder durchlaufen wird. Bei jedem Durchgang wird zu I Eins dazu gezählt und das Ergebnis ins Ausgabe Fenster geschrieben.



    Alles, was man in das Lua Skript geschrieben hat, wird nur ausgeführt, so lange das Hauptprogramm läuft. Wenn das stoppt, dann ist Lua beendet und hat keinen Einfluss mehr auf irgendetwas.



    Fazit:
    Ihr könnt die Festlegung der Variable
    I = 1
    und die Aufgabe
    I = I +1
    und die Aufgabe
    print(I)
    löschen, ohne das etwas schlimmes passiert.


    Die Zählerei hört auf und das Ausgabefenster bleibt leer.
    Die Funktion EEPMain() läuft trotzdem weiter und Lua arbeitet anstandslos.



    Aber solch ein Lebenszeichen kann sehr nützlich sein.
    Denn ohne diese Hilfe kann man nicht erkennen, ob Lua rennt ;)



    In der Funktion EEPMain() steht zum Schluss noch etwas. Nämlich
    return 1


    Das Wort return ist ebenfalls ein Befehl, der in Lua fest definiert ist.
    In der englischen Sprache bedeutet return in etwa zurückgeben.


    Das ist jedenfalls in Lua damit gemeint
    return 1
    heißt „Gib den Wert 1 zurück“



    Am Anfang hatte ich beschrieben, wie man sich in einer Funktion das Ergebnis einer Aufgabe in einer Variable merken kann, um dieses Ergebnis später zu benutzen. Der Befehl return ist eine alternative Möglichkeit. Er macht es etwas bequemer das Ergebnis einer Funktion zu benutzen.


    Ohne den Befehl return müsste ich folgendes tun, um das Ergebnis einer Aufgabe zu benutzen:


    Ergebnis = 0


    function Ausrechnen()
    Ergebnis = 1 + 2
    end



    Ausrechnen()
    print (Ergebnis)


    Ich müsste also zuerst eine Variable festlegen,
    dann die Funktion definieren
    dann die Funktion aufrufen
    und zuletzt das Ergebnis verwenden.



    Mit return geht das mit etwas weniger Aufwand:


    function Ausrechnen()
    return 1 + 2
    end


    print (Ausrechnen())


    Diesmal brauche ich keine zusätzliche Variable, sondern nur die Funktion.
    In der Funktion steht, was sie zurück geben soll, wenn ich sie aufrufe.


    Und wenn ich dann an anderer Stelle - im Beispiel mit dem Befehl print() - den Funktionsnamen benutze, dann passiert dort alles auf einmal: Die Funktion wird aufgerufen, erledigt ihre Aufgabe, gibt das Ergebnis zurück und setzt es dort ein, wo ich die Funktion aufgerufen habe.


    Das ist eine ziemlich abstrakte Sache und gewiss nicht leicht nachvollziehbar.
    Aber es ist ein Hinweis darauf, wofür der Befehl return gut ist.



    Im speziellen Fall der Hauptfunktion EEPMain() wird return 1 benutzt, um die Funktion erneut aufzurufen, sobald sie durchlaufen wurde. Dass das passiert steht nirgendwo im Skript, sondern wurde sozusagen in die spezielle Funktion EEPMain() mit eingebaut.


    Würde man die Zeile
    return 1
    löschen, dann liefe die Funktion EEPMain() nur ein einziges Mal und anschließend würde Lua sofort beendet.



    Dieses return 1 ist also sozusagen die Vorbereitung für ein Notaus, welches man selber in das Lua Skript einbauen könnte.
    Aber das müssen wir uns für später aufheben. Viel später!

    "what's the downside to eating a clock?"

    - Joel -

    The post was edited 3 times, last by Michael89: Korrekturen für Goetz ().

  • Okay, wenn ich eine Funktion festlegen will, dann tu ich das mit dem Befehl function.
    Aber was hat es mit den Klammern hinter den Funktionsnamen auf sich?


    Die sind ein spezieller Speicherplatz, der für diese Funktion bereit gestellt wird.
    Denn oft möchte man an eine Funktion etwas übergeben, das sie für ihre Aufgaben benutzen soll.


    Ein prima Beispiel ist die Funktion print ()
    Der möchte ich nämlich sagen, was sie drucken bzw. ausgeben soll.


    Und das kommt in die Klammern.


    Genauso kann ich es auch mit der Funktion Ausrechnen() tun, wenn ich sie entsprechend festlege:


    function Ausrechnen(Beigabe)
    Beigabe = Beigabe + 1
    return Beigabe
    end



    Wenn ich jetzt an anderer Stelle schreibe
    Ausrechnen(1)
    dann wird die Funktion Ausrechnen() aufgerufen und die Variable Beigabe in der Funktion bekommt den Wert 1.
    Die Funktion rechnet dann zu der Variablen Eins dazu, speichert das Ergebnis wieder in der Variablen Beigabe und gibt den gespeicherten Wert zurück.


    Steht in meinem Programm an einer Stelle
    print(Ausrechnen(1))
    dann bekomme ich im Ausgabefenster die Zahl 2


    Ich rufe nämlich die Funktion print() auf und sage ihr in den Klammern, dass sie die Funktion Ausrechnen() mit dem Wert 1 durchführen und das Ergebnis ausdrucken soll.

  • Per PN hat mich SW-I auf einen wirklich schlimmen Fehler aufmerksam gemacht


    im Beitrag 19 nutzt Du die Variable "I". Das machte mich erst etwas unsicher, weil es auf dem Rechner auch so einen Strich gibt, der kein Buchstabe ist.


    Wichtiger: Du schreibst dann "Print (i)", also kleines "I".
    Falls das einen Fehler gibt, ist es evtl. besser, den Beitrag zu editieren ( i --> I ).


    Ja, das gibt einen Fehler.
    Denn ich schrieb ja im ersten Kapitel, dass jede andere Schreibweise eine andere Variable erzeugt.


    john ist nicht das selbe wie John


    Und das kleine i ist nicht das selbe wie das große I


    Beim Buchstaben i sieht man das leider nur sehr schwer. Und ich hatte überlegt, ob ich für die Erklärung nicht besser einen anderen, lesbaren Buchstaben verwende.
    Auch deshalb, weil das große i wie ein Strich aussieht und nicht wie ein Buchstabe.



    Aber in den EEP Skripten wird nunmal das große I benutzt.
    Und mir war wichtig, dass ihr das wiedererkennt. Dass ihr euch erinnert und denkt: "Ich weiß, warum das da steht. Das hat Götz mir erklärt."


    In den Skripten wurde deshalb das I verwendet, weil es Programmier-Tradition ist.
    Immer, wenn etwas gezählt werden soll, speichern Programmierer diese Zahl in einer Variablen i oder I.


    Meist nehmen sie dafür das kleine i.


    Und genau deshalb hat Trend das große I für diesen Zähler am Anfang benutzt.
    Damit das kleine i für uns frei bleibt.


    Im EEP Skript steht richtig
    print(I)
    also mit großem I


    Stünde dort das kleine i, dann würde print() nicht die Zahl ausgeben, die im großen I gespeichert wurde.



    Noch einmal:
    Das i wird traditionell für Zähler verwendet.
    Weil Programmierer gedanklich das Wort "integer" damit verbinden.
    "integer" steht für ganzzahlig. Also nur ganze Zahlen. Keine Brüche.
    Man zählt ja meistens 1 ... 2 ... 3 ...
    Und nicht 1 ... 1,001 ... 1,002 ...


    Prinzipiell kann man aber jeden Namen für diese Variable nutzen


    Also beispielsweise auch das A


    A = 0


    function EEPMain()
    A = A+1
    print(A)
    return 1
    end



    oder das Wort Anzahl


    Anzahl = 0


    function EEPMain()
    Anzahl = Anzahl+1
    print(Anzahl)
    return 1
    end


    In allen Varianten passiert genau das gleiche.



    Aber wenn ich schreibe


    A = 0


    function EEPMain()
    A = A+1
    print(a)
    return 1
    end


    dann passiert etwas anderes. Weil ich A hoch zähle, aber a auf dem Bildschirm ausgebe.



    Vielen Dank, SW-I, für diesen sehr wichtigen Hinweis!
    Und danke auch dafür, dass du ihn mir wie gewünscht per PN geschickt hast!


    Gruß
    Götz

  • In einem Programm oder Skript werden auch Texte benötigt. Lua muss unterscheiden können, was als Text gemeint ist und was beispielsweise eine Variable oder ein Befehl sein soll. Ein Computer kann nicht mitdenken. Der kann eigentlich überhaupt nicht denken. Ein Computer ist strunzdumm und tut nur stupide das, was man ihm sagt.


    Wenn ich also, wie im ersten Beispiel, schreiben kann
    John = 3
    und damit festlege, dass eine Variable den Namen John bekommen soll und ich in dieser Variablen den Wert 3 speichern möchte, dann bedeutet das folgendes:
    Die Entwickler von Lua haben festgelegt, dass ein Wort im Programm, welches in der Sammlung der Befehle von Lua nicht vorkommt, als Variable interpretiert wird. Völlig egal, wo im Skript dieses Wort auftaucht.


    Die Funktion
    print(Modellbahn)
    schreibt also nicht das Wort Modellbahn ins Ausgabefenster.
    Sondern sie schaut nach, ob in der Variablen Modellbahn ein Wert gespeichert wurde.
    Und diesen Wert schreibt sie dann ins Ausgabefenster.


    Falls dem Wort Modellbahn nie ein Wert zugewiesen wurde, schreibt die Funktion print() das Wort nil ins Ausgabefenster.
    nil ist Englisch und meint nix oder nüscht.


    Damit man Texte in einem Skript verwenden und beispielsweise ins Ausgabefenster schreiben kann, muss man sie als Text kennzeichnen. Dafür verwendet man die Anführungsstriche.
    "Modellbahn"
    zeigt Lua, dass man das Wort Modellbahn als Text benutzen möchte und nicht als Variable.


    Will ich das Wort Modellbahn ins Ausgabefenster schreiben, dann lautet der richtige Code dafür
    print("Modellbahn")


    Alles ganz einfach, oder?



    Von wegen!
    Hier ist gar nichts einfach. Im Gegenteil.
    Lasst euch nicht davon täuschen, dass das so einfach aussieht.


    Denn wenn man sich das mal ganz genau ansieht, dann erkennt man folgendes:
    Es steht ein Anführungszeichen vor dem Text und eins dahinter.
    Wir betrachten das im Ganzen. Weil unser Gehirn das kann.
    Für uns steht das Wort in Anführungszeichen.


    So etwas geniales kann ein Computer nicht.
    Der muss das Skript Buchstabe für Buchstabe und Zeichen für Zeichen lesen.


    Lua sieht an einer Stelle plötzlich ein Anführungszeichen und weiß damit:
    Ab hier kommt Text
    Und wenn Lua dann wieder ein Anführungszeichen sieht, dann weiß es:
    Hier hört der Text wieder auf.


    Das selbe Anführungszeichen bedeutet also einmal Text-Anfang und einmal Text-Ende.
    Je nachdem, ob vorher schon ein Anführungszeichen stand oder nicht.



    Wer schon einmal Aufnahmen mit einer Videokamera gemacht hat, der kennt wahrscheinlich folgendes Problem:
    Mit der Start-Stopp Taste kann man die Aufnahme abwechselnd starten und stoppen. Und wehe, man kommt dabei aus dem Rhythmus. Dann hat man später nichts von dem aufgenommen, was man wollte. Sondern nur den Himmel, die eigenen Füße, das Innenleben der Jackentasche … Je nachdem, was man mit der Videokamera gemacht hat, als man dachte, sie sei auf Pause.



    Wir kämpfen bei der Videokamera und bei Texten in Programmier- und Skriptsprachen mit dem selben Problem:
    Ein einziger Befehl - der Druck auf die Start-Stopp Taste bzw. das Anführungszeichen - hat zwei gegensätzliche Bedeutungen. Er steht abwechselnd für An und Aus.
    Und man muss höllisch aufpassen, dass man nicht aus dem Tritt kommt.


    Ein einziges Anführungszeichen zu viel oder zu wenig im Skript kann alles durcheinander bringen!




    Okay, zurück zu Texten:
    Ich kann also schreiben
    print("Lua rennt")
    und im Ausgabefenster steht dann
    Lua rennt
    und zwar ohne die Anführungszeichen!
    Denn die gehören nicht zum Text, sondern zeigen Lua, dass alles, was dazwischen steht, als Text behandelt werden soll.



    Man kann Texte auch in Variablen speichern. Das ist sehr praktisch, wenn man sie öfter benötigt.
    Wenn ich schreibe
    John = "Lua rennt"
    dann merke ich mir den Text Lua rennt in der Variablen John.


    print(John)
    gibt dann im Ausgabefenster Lua rennt aus.


    Wenn ich hingegen
    print("John")
    schreibe, dann wird das Wort John im Ausgabefenster erscheinen und nicht der Text, den ich in der Variablen John gespeichert habe.



    Und was passiert, wenn ich
    print(john)
    schreibe?


    Dann gibt Lua im Ausgabefenster folgenden Text aus:
    nil: 0x0000000000000000


    Denn der Variablen john - mit kleinem j am Anfang - habe ich nie einen Wert zugewiesen.
    Da steht nix drin. Und das englische Wort für nix ist nil



    Man muss bei Programmier- und Skriptsprachen peinlich genau aufpassen, was man schreibt.
    Ein einziges falsches Zeichen bringt alles durcheinander.


    Übrigens wird eine Software wie EEP genau so programmiert wie ihr es jetzt mit Lua kennen lernt. Nur, dass da eine unvorstellbare Menge an Code Zeilen nötig ist. Für jeden Pups, den das Programm machen soll, muss man zig Zeilen Code schreiben.
    Dafür, dass etwas passiert, wenn man auf einen Button drückt.
    Dafür, dass dieser Button überhaupt auf dem Bildschirm erscheint.
    Dafür, dass auf diesem Button ein kleines Bildchen zu sehen ist.
    Dafür, dass man ein Gleis anfassen kann.
    Dafür dass sich die Darstellung dieses Gleises bewegt, wenn man es verschiebt …


    Und ein einziges, falsches Zeichen in diesem Code bringt die Dinge durcheinander.
    Das nennt man dann einen Bug.
    Vielleicht könnt ihr euch nun besser vorstellen, wie schwer es ist einen Bug zu finden?
    Vor allem dann, wenn die Bugmeldung nur lautet ”Das Programm stürzt dauernd ab!“ und der Programmierer jetzt theoretisch einen Text, der so lang wie Goethes Faust ist, nach einem einzigen fehlenden oder falschen Zeichen durchsuchen müsste, um die Ursache zu finden?

  • bzw. im Thema ”Text“


    Benny hat mich gerade in einer PN darauf hingewiesen, dass ich manchmal die falschen Anführungszeichen benutze.
    Das ist auch so ein Stolperstein, der einem den schönsten Code versauen kann.


    Es gibt nämlich eine Reihe verschiedener Anführungszeichen.

    • solche zum Beispiel: „Für deutsche Texte, die Anführungszeichen unten und oben haben sollten.“
    • oder die sehr ähnlichen, englischen bzw. amerikanischen: ”Die Briten haben vorne und hinten die Anführungsstriche oben.“
    • und dann gibt es noch diese ganz einfachen: "Die sind gerade, nicht geschwungen und stehen ebenfalls vorne und hinten oben."


    Das sind alles verschiedene Zeichen für Lua. So verschieden wie ! und % und #
    Und nur die einfachen, geraden Anführungszeichen aus dem dritten Beispiel in der Liste akzeptiert Lua als Kennung für einen Text.


    Wenn man seinen Code im vorgesehenen Skript Fenster schreibt, dann bekommt man ganz automatisch die richtigen Anführungszeichen. Aber mein Tutorial habe ich mit einem Schreibprogramm geschrieben und dann ins Posting eingefügt. Und die meisten Schreibprogramme gehen heute davon aus, dass man einen ”schönen Text“ schreiben möchte. Deshalb bekommt man die ”schönen“ Anführungszeichen.


    Wenn ich also mit einem Programm wie Wordpad meinen Lua Code schreiben und dann mittels Kopieren und Einfügen in das Skript einfügen würde, dann hätte ich die falschen Anführungszeichen. Text würde dann von Lua nicht als Text erkannt und ich bekäme ”Müll“.


    Mit anderen Worten:


    print("Lua rennt") funktioniert
    print(„Lua rennt“) funktioniert nicht!
    print(”Lua rennt”) funktioniert auch nicht!


    und man muss sehr genau hinschauen, um den Unterschied zu erkennen.

  • Operatoren sind das, was Programme clever macht. Weil sie dafür sorgen, dass der Computer etwas rechnet, prüft oder ändert.


    Ich habe in den vorigen Kapiteln schon Operatoren benutzt.


    Zum Beispiel das = Zeichen für die Codezeile
    John = 3


    Oder das + Zeichen für
    Gleis = 2+1
    Gleis = Gleis+1



    Viele dieser Zeichen leuchten sofort ein.
    Vermutlich hat sich niemand gefragt, warum ein + Zeichen dafür sorgt, dass zwei Werte addiert werden?
    Ist doch logisch. Dafür ist so ein + doch da, oder?
    Und dieses = Zeichen bedeutet ”gleich“. Weiß doch jedes Kind.
    Oder?


    Wir verstehen diese Dinge alle, weil wir sie irgendwann gelernt und längst verinnerlicht haben.
    Ein Computer nicht. Der hat überhaupt keine Ahnung. Dem muss man wirklich alles sagen.
    Zum Glück haben das die Entwickler von Programmiersprachen für uns schon getan.


    Genau das ist nämlich eine Programmier- und eine Skriptsprache:
    Ein Programm, welches unsere Texte in Befehle für den Computer übersetzt.
    Und je nach Programmiersprache sind diese Übersetzungen unterschiedlich. Deshalb ist Lua etwas anders als Basic. Die Übersetzungen, die dort eingebaut wurden, sind verschieden.


    Einer dieser Unterschiede ist das = Zeichen.


    In der Umgangssprache ergibt sich aus dem gesamten Satz, ob ich etwas ”gleich“ machen möchte oder fragen will, ob etwas ”gleich“ ist.


    In Lua nicht. Da muss ich durch die Schreibweise zeigen, was ich meine.


    Wenn ich nur ein = schreibe, dann mache ich etwas gleich.
    So, wie in
    John = 3


    Dabei ist auch die Reihenfolge wichtig. Denn Lua kann nicht mitdenken.
    Bei der Zeile oben scheint sofort klar zu sein, was gemeint ist.
    Aber was ist bei
    John = Ringo
    Wer wird hier wem gleich gemacht?


    Das hat man in Lua festgelegt. Das, was vor dem = Zeichen steht, wird verändert.
    Also hat die Variable John nach der obigen Codezeile den gleichen Wert, der in Ringo gespeichert war.
    Der alte Wert in John wird also mit dem Wert aus Ringo überschrieben. Der Wert in Ringo ändert sich dabei nicht!


    Und weil diese Reihenfolge fest steht, kann man auch nur
    John = 3
    schreiben, wenn man möchte, dass in der Variablen John der Wert 3 gespeichert wird.
    3 = John geht nicht!



    Nun möchten wir manchmal aber auch wissen, ob der Wert in der Variablen John gleich 3 ist oder nicht.


    Das geht nicht mit
    John = 3
    weil man damit den Wert 3 in John speichert.
    Falls John also vorher nicht 3 war, dann werde ich es mit dieser Codezeile nie erfahren.
    Denn erstens sagt sie mir nicht, ob eine 3 in John gespeichert war. Und zweitens ist die Zahl, die da vorher drin war, jetzt auch noch durch die 3 ersetzt worden.


    Deshalb brauchen wir in Lua für die Frage, ob der Wert von John vielleicht 3 ist, eine eigene Schreibweise.
    Nämlich zwei = Zeichen direkt hintereinander. Also
    John == 3
    Das ist ein Vergleich und als Ergebnis bekomme ich entweder ”wahr“ oder ”falsch”.


    Manchmal möchte man auch wissen, ob der Wert in John 3 oder mehr ist.
    Die Schreibweise dafür ist
    John >= 3
    das > Zeichen steht für ”größer”.
    Also bekomme ich als Ergebnis dieser Prüfung ”wahr“, wenn der Wert in John größer oder gleich 3 ist. Falls der Wert kleiner als 3 ist bekomme ich ”falsch”.


    Natürlich kann ich auch
    John <= 3
    schreiben und so fragen, ob der Wert in John kleiner als 3 oder gleich 3 ist.



    Will man nur wissen, ob der Wert in der Variablen John größer als 3 ist, dann schreibt man
    John > 3
    Falls in der Variablen der Wert 3 steht, dann ist die Antwort diesmal ”falsch”, wohingegen bei
    John >= 3
    die Antwort ”wahr” wäre.



    Abfragen und Berechnungen kann man auch in einer Zeile zusammenfassen
    John > 1+2
    rechnet zuerst 1+2 aus und prüft dann, ob der Wert in der Variablen John größer ist.



    Gelegentlich möchte man auch nur wissen, ob in der Variablen John irgendeine andere Zahl als 3 gespeichert ist. Egal, ob die nun größer oder kleiner (oder vielleicht überhaupt keine Zahl) ist. Die Schreibweise dafür ist
    John ∼= 3
    also zuerst die Welle und dann das = Zeichen.
    Als Ergebnis bekommt man dann ”falsch”, wenn in John der Wert 3 steht. Steht in John keine 3, dann ist die Antwort ”wahr”.


    Die hier gezeigten Reihenfolgen für ∼= und >= und <= müssen meines Wissens unbedingt eingehalten werden.



    Einfache Rechenaufgaben werden so geschrieben, wie man es vermuten würde
    + zum Addieren
    - zum Subtrahieren
    * für die Multiplikation
    / für die Division


    Bei der Multiplikation muss man darauf achten den Stern * und nicht ein kleines x zu verwenden.
    Und bei der Division muss es der Schrägstrich vorwärts / sein. Keinesfalls der Schrägstrich rückwärts \



    Im folgenden Kapitel werde ich ein paar spezielle Operatoren erläutern

  • In verschiedenen Threads wurde schon nach der Bedeutung des % Zeichens gefragt und darauf gab es ein paar gute, mathematische Erklärungen.


    Aber Mathe ist nicht jedermanns Sache. Deshalb möchte ich ergänzend einen anderen Weg versuchen.


    Stellt euch eine Uhr vor.
    Eine Uhr, die nur einen Stundenzeiger hat, der von einer Stunde zur nächsten springt.


    [Blocked Image: http://up.picr.de/19045468wp.jpg]


    Wenn diese Uhr auf 1 steht und man 3 dazu zählt, dann steht sie anschließend auf 4.
    Steht die Uhr aber auf 10 und man rechnet 3 hinzu, dann steht die Uhr anschließend nicht auf 13, sondern auf 1.
    Weil sie nach 12 immer wieder von vorne beginnt.


    Es ist völlig egal, wie viel Stunden vergehen - die Uhr zeigt immer nur einen Wert zwischen 1 und 12.



    Und so ist der % Operator gedacht.
    Nur, dass man ihm sagen kann, wie viele Stunden auf der Uhr stehen sollen


    13 % 12 ergibt 1 (wie bei der Uhr)
    13 % 10 ergibt 3, denn diese Uhr hat nur 10 Stunden
    23 % 10 ergibt ebenfalls 3
    23 % 5 ergibt ebenfalls 3. Die Uhr hat nur 5 Stunden. Aber wenn ich bei 0 Uhr anfange und 23 Stunden dazu zähle, dann lande ich auch hier bei 3 Uhr. Ich komme nämlich vier mal rum und habe dann noch drei Stunden übrig.


    Und nun komme ich doch mit Mathematik:
    Wie in einem anderen Thread schon erläutert wurde, kann ich 20 sauber durch 5 teilen. Es bleibt ein Rest von 3.


    Aber einen wichtigen Unterschied zur richtigen Uhr gibt es:
    Oben steht nicht die 12 (beziehungsweise der Wert, den man rechts vom % Zeichen schreibt), sondern 0


    10 % 10 ergibt also 0 und nicht 10



    % 5 erlaubt also alle Werte von 0 bis 4.
    Aus der 5 wird wieder die 0.
    Aus der 6 die 1
    Aus 7 wird 2
    etc.



    Im Skript zum ersten Lua Tutorial steht
    route % 2
    Das Ergebnis kann nur 0 oder 1 sein


    Genauer betrachtet steht dort
    route % 2 == 1


    Das Ergebnis von route % 2 wird also mit der Zahl 1 verglichen.
    Wenn das Ergebnis von route % 2 den Wert 1 hat, dann ist das Ergebnis des Vergleichs "wahr".


    Weil die Variable in der Zeile
    route = route + 1
    bei jedem Durchgang weitergezählt wird, ist das Ergebnis von
    route % 2
    immer abwechselnd 0 oder 1. Und damit ist das Ergebnis des Vergleichs immer abwechselnd "falsch" oder "wahr"



    Hinter
    route % 2
    verbirgt sich also ein einfacher Trick, um immer abwechselnd mal so und mal so zu entscheiden.

  • Die vorherigen Kapitel reichen noch nicht, um irgendetwas auf einer Anlage per Lua zu steuern.


    Aber es wird Zeit einmal auszuprobieren, ob man Lua nun überhaupt dazu bringen kann etwas zu tun.
    Und zwar nicht irgendwas. Sondern exakt das, was man will.


    Erinnert euch an eure erste Fahrstunde (falls ihr nicht zu jung seid und die erst noch vor euch habt ;) )
    Da seid ihr nicht gleich ins Nachbardorf gefahren. Und auch nicht zum Bäcker um die Ecke.
    Ihr habt überhaupt nichts gemacht, was irgendwie mit dem Verkehr auf unseren Straßen zu tun hatte.
    Sondern ihr habt Kreise auf einem großen Parkplatz gedreht, um zunächst ein Gefühl für das Auto zu bekommen.
    Und ich weiß noch heute, wie überrascht ich dabei war. Das ging nämlich alles nicht so leicht, wie ich erwartet hatte.
    Anfahren, Lenken, Bremsen …
    Was von außen sooo leicht ausgesehen hatte, war plötzlich ziemlich schwer.
    Und es erforderte mehr Konzentration als ich geahnt hatte.



    Mit dem Auto namens "Lua" ist das nicht anders.
    Dafür muss man erst einmal ein Gefühl bekommen. Das kann man nämlich auch "abwürgen".



    Deshalb möchte ich vorschlagen, dass ihr das mit einer neuen, komplett leeren Anlage versucht.
    Die ist sozusagen euer großer Parkplatz für die ersten Versuche.


    Sobald die leere Anlage geöffnet ist, macht ihr das Skript Fenster auf und löscht alles, was dort drin steht.
    Komplett!


    Also auch die EEPMain() Funktion.


    Damit - um beim Vergleich mit dem Parkplatz zu bleiben - nichts im Weg steht, was ihr anrempeln könntet.


    Und dann schreibt ihr in das Skript Fenster nur eine einzige Zeile:


    print("Lua rennt")


    Das wird sozusagen euer erster Blick in den Rückspiegel.


    Wir müssen den Rückspiegel jetzt nur noch finden …



    Sucht unten am Fensterrand den Knopf "Skript neu laden" und drückt ihn.
    Das ist nötig, damit EEP das übernimmt, was ihr in das Skript Fenster geschrieben habt.


    Das Skript Fenster schließt sich und das Skript, welches ihr eben geschrieben habt, wird an EEP übertragen.


    Öffnet die Programmeinstellungen und sucht in der rechten Spalte ganz unten "EEP Ereignis Fenster"
    Das muss aktiviert sein, wenn man die Ausgabe von print() sehen möchte.


    Und wenn ihr jetzt in den 3D Modus der Anlage wechselt, dann müsstet ihr im Ausgabefenster die Schrift Lua rennt sehen.
    Also den Text, den ihr zwischen die Klammern hinter print geschrieben habt. Aber ohne die Anführungszeichen.


    Das sieht nicht so spannend aus wie ein 20-gleisiger, vollautomatisierter Schattenbahnhof mit Zufallssteuerung.


    Aber weil so wenig passiert, kann man ganz genau hinschauen und jedes Detail bemerken.


    Und genau darum geht es.
    Also zum Beispiel um die Tatsache, dass die Anführungsstriche nicht mit ausgegeben werden. Weil sie ein Steuerzeichen sind welches Lua benötigt, um Buchstaben als Text zu erkennen und nicht als Variablen.


    Außerdem steht noch etwas im Ausgabefenster. Nämlich
    Error running function 'EEPMain': attempt to call a nil value


    Merkwürdig, oder?
    Wir haben doch gar keine EEPMain Funktion mehr im Skript. Wie kann die da eine Fehlermeldung produzieren?


    Zeit für einen weiteren Test, um der Sache auf die Spur zu kommen.


    Dafür baut ihr jetzt bitte in das Skript eine eigene Funktion ein.


    Öffnet also das Skriptfenster.
    Dort sollte jetzt
    print("Lua rennt")
    zu lesen sein. Genau, wie ihr es rein geschrieben hattet.


    Lasst dahinter (für bessere Lesbarkeit) eine Zeile frei und schreibt dann
    function druckMich()
    print("Hier meldet sich die Funktion")
    end


    Mehr nicht!


    Drückt wieder auf "Skript neu laden" unten rechts im Skriptfenster.


    Sofort wird das Fenster geschlossen und das Skript übertragen.
    Außerdem wird es sofort ausgeführt, wenn ihr im 3D Fenster geblieben seid.
    Falls ihr vorher in den 2D Modus gewechselt habt, dann müsst ihr jetzt wieder zurück zu 3D, damit das neue Skript ausgeführt wird.


    Im Ausgabefenster sollte jetzt stehen
    Lua rennt
    Error running function 'EEPMain': attempt to call a nil value
    Lua rennt
    Error running function 'EEPMain': attempt to call a nil value


    Nicht ärgern, dass das nicht das ist, was ihr erwartet hattet. Sondern Schlüsse daraus ziehen.
    Das geht besser, wenn ihr gleich noch eine Änderung am Skript vornehmt.


    Öffnet das Skriptfenster wieder und ändert die erste Zeile in
    print("Lua verwirrt mich")
    aber lasst den Rest so, wie er ist.


    Drückt "Skript neu laden" und im Ausgabefenster müsste jetzt stehen:
    Lua rennt
    Error running function 'EEPMain': attempt to call a nil value
    Lua rennt
    Error running function 'EEPMain': attempt to call a nil value
    Lua verwirrt mich
    Error running function 'EEPMain': attempt to call a nil value


    Aaaahhh … Das Ausgabefenster wird also nicht automatisch beim Start gelöscht.
    Die ersten vier Zeilen sind von den ersten beiden Versuchen stehen geblieben.


    Okay, das ist schonmal eine nützliche Erkenntnis.


    Und wo ist der Text aus dem print() Befehl, den wir in die Funktion druckMich() geschrieben haben?
    Nicht zu sehen …
    Weil diese Funktion nie aufgerufen wird. Der Befehl function sorgt nur dafür, dass Lua lernt, was in dieser Funktion zu tun ist.



    Nächster Test:
    Öffnet bitte wieder das Skript. Geht ganz ans Ende und fügt eine weitere Zeile ein:
    druckMich()


    Das ganze Skript sollte nun so aussehen:
    print("Lua verwirrt mich")


    function druckMich()
    print("Hier meldet sich die Funktion")
    end


    druckMich()



    Anschließend "Skript neu laden" und im Ausgabefenster sollte jetzt folgendes stehen:
    Lua rennt
    Error running function 'EEPMain': attempt to call a nil value
    Lua rennt
    Error running function 'EEPMain': attempt to call a nil value
    Lua verwirrt mich
    Error running function 'EEPMain': attempt to call a nil value
    Lua verwirrt mich
    Hier meldet sich die Funktion
    Error running function 'EEPMain': attempt to call a nil value


    Die letzten drei Zeilen sind bei diesem Test hinzu gekommen. Der Rest ist das, was wir bei den vorigen Versuchen ausgegeben hatten.


    Okay, eine Funktion muss also aufgerufen werden, damit sie überhaupt etwas tun kann.


    Zeit zu probieren was passiert, wenn man eine Funktion aufruft, die es nicht gibt.


    Erweitert euer Skript am Ende um eine weitere Zeile:
    michGibtsNicht()
    und achtet unbedingt auf die Klammern am Ende. Die sagen Lua, dass hier eine Funktion aufgerufen werden soll.


    Lasst das Skript laufen und ihr bekommt:
    Lua rennt
    Error running function 'EEPMain': attempt to call a nil value
    Lua rennt
    Error running function 'EEPMain': attempt to call a nil value
    Lua verwirrt mich
    Error running function 'EEPMain': attempt to call a nil value
    Lua verwirrt mich
    Hier meldet sich die Funktion
    Error running function 'EEPMain': attempt to call a nil value
    Lua verwirrt mich
    Hier meldet sich die Funktion
    Error running function 'michGibtsNicht': attempt to call a nil value
    Error running function 'EEPMain': attempt to call a nil value


    Bemerkenswert ist hier die vorletzte Zeile!
    Wenn man eine Funktion aufruft, die nicht definiert wurde, dann kommt genau die Fehlermeldung, die wir schon beim ersten Mal (für die Funktion EEPMain) gesehen hatten.


    Ihr wisst, wo die Funktion michGibtsNicht() aufgerufen wird. Ihr habt den Aufruf ja selbst ins Skript geschrieben.
    Aber wer ruft denn dauernd diese Funktion EEPMain() auf, die es gar nicht gibt? Die steht doch nirgendwo im Skript?
    Die Antwort ist: EEP macht das. Dieser Aufruf ist fest in EEP eingebaut.

  • Zeit aufzuräumen, bevor wir mit unseren Tests fortfahren.


    Mit der Zeit wird es doch sehr unübersichtlich, wenn das Ausgabefenster alles behält, was man dort hinein geschrieben hat.


    Zum Glück hat das Ausgabefenster einen Löschknopf. Drückt man auf den runden, blauen Knopf mit dem i darauf, dann findet man als letzten Punkt in der Liste "Lösche EEP Log"


    Bequemer ist es natürlich, wenn Lua selbst aufräumt. Dafür gibt es in Lua eine fest definierte Funktion. Nämlich
    clearlog()


    Der Name dieser Funktion setzt sich aus zwei Worten zusammen:
    clear ist das englische Wort für klar.
    und log ist das englische Wort für ein Protokoll


    Schreibt man an den Anfang des Skripts die Zeile
    clearlog()
    dann wird zuerst das Ausgabefenster geleert und dann alles andere ausgeführt.



    Es gibt einige Funktionen, die in Lua fest definiert sind.
    clearlog() ist eine davon. Die Funktion muss man nicht erst schreiben, weil sie in Lua schon enthalten ist.
    print() ist ebenfalls eine dieser fest definierten Funktionen.


    Aber die Funktion EEPMain() ist in Lua nicht definiert.
    Die muss man selbst schreiben. Denn für jede Anlage wird eine andere, passende EEPMain() Funktion benötigt.



    Vorsicht! Bitte nicht verwechseln!
    print() ist in Lua definiert, muss aber im Skript aufgerufen werden um etwas zu machen.
    EEPMain() ist nicht in Lua definiert, wird aber von EEP automatisch aufgerufen.



    Und was muss nun in dieser Funktion EEPMain() drin stehen? Das untersuchen wir in diesem Kapitel genauer.


    Ich würde vorschlagen, dass ihr wieder mit einer leeren Anlage beginnt. Dort öffnet ihr das Lua Skript Fenster und löscht alles, was dort drin steht. Komplett.


    Dann schreibt ihr rein
    function EEPMain()
    und sonst nichts ...


    Drückt auf "Skript neu laden". Ihr bekommt
    [string "EEP Script"]:1: 'end' expected near<eof>
    und das Skript Fenster bleibt geöffnet.


    Frei übersetzt heißt das "Im EEP Skript wird vor dem Ende des Skripts ein 'end' erwartet.


    Lua hat nämlich bei der Übertragung des Skripts geprüft, ob jede Funktion, die definiert wurde, auch ein Ende hat. Und unsere EEPMain() Funktion hat keins.


    Wir müssen also mindestens schreiben:
    function EEPMain()
    end


    Dann erneut auf "Skript neu laden" drücken und beobachten, was passiert:


    Im Ausgabefenster steht noch die Fehlermeldung von zuvor und dann eine zweite, die lautet
    Error wrong result type #1
    was übersetzt heißt
    Fehler: falsches Ergebnis Typ Nummer 1


    Wer immer die Funktion EEPMain() aufgerufen hat, erwartet offenbar ein Ergebnis.


    Funktionen können nämlich Daten bekommen und Daten wieder zurückgeben. Man könnte das vielleicht mit einem Verstärker vergleichen. Wie ein Verstärker haben auch Funktionen Ein- und Ausgänge.


    Der Eingang einer Funktion sind die Klammern hinter dem Namen.


    Und für den Ausgang gibt es den Befehl return



    Bei EEPMain() interessiert nur der Ausgang. Im Kapitel "Funktionen" hatte ich schon einmal kurz angesprochen, dass Lua angehalten wird, wenn am Ende der Funktion EEPMain() das
    return 1
    fehlt. Das will ich jetzt etwas detaillierter erklären.



    Probiert mal aus was passiert, wenn ihr stattdessen
    return 2
    schreibt. Im Ganzen also:


    function EEPMain()
    return 2
    end


    Löscht den Inhalt des Ausgabefensters, drückt auf "Skript neu laden", wechselt (wenn nötig) ins 3D Fenster und ihr seht im Ausgabefenster ... nichts.
    Das ist jetzt nicht besonders aussagekräftig. Aber immerhin gibt es keine Fehlermeldung.


    Besser wäre natürlich, wenn im Ausgabefenster etwas stehen würde was uns sagt, dass überhaupt etwas passiert.


    Also erweitern wir das Skript ein wenig.
    function EEPMain()
    print("Lua rennt")
    return 2
    end


    Ausgabefenster löschen, Skript neu laden und wenn man jetzt im 3D Modus ist, dann erscheint im Ausgabefenster
    Lua rennt
    Und zwar immer wieder. Zeile für Zeile untereinander.


    Daraus kann man erkennen, dass die Funktion EEPMain() von EEP immer wieder aufgerufen wird.


    Jetzt kann man bei return andere Zahlen probieren. Und stellt fest, dass sich dadurch nichts ändert. Nimmt man hingegen etwas anderes als eine Zahl und schreibt zum Beispiel:
    return "nimm das"
    dann bekommt man wieder die Fehlermeldung
    Error wrong result type #1


    Wenn EEPMain() von EEP aufgerufen wird, dann erwartet EEP, dass es eine Zahl zurück bekommt. Richtiger gesagt etwas vom Typ "Zahl". Aber was es in Lua und anderen Programmiersprachen mit diesen Typen auf sich hat, möchte ich an anderer Stelle erklären.


    Welche Zahl man in EEPMain() zurück gibt, ist EEP egal.
    Mit einer Ausnahme. Schreibt man
    return 0
    dann wird die Funktion EEPMain() nicht erneut aufgerufen.


    Probiert es aus. Schreibt:
    function EEPMain()
    print("Lua rennt")
    return 0
    end


    Löscht das Ausgabefenster, ladet das Skript neu, wechselt (wenn nötig) ins 3D Fenster und im Ausgabefenster steht
    Lua rennt
    genau ein einziges Mal. Mehr nicht. Es gibt also keine Fehlermeldung und die EEPMain() Funktion wird kein zweites Mal aufgerufen.


    Man hat also bei der Implementierung der EEPMain() Funktion in EEP daran gedacht, dass man unter Umständen vielleicht den ständigen Aufruf stoppen möchte.


    Solange Lua mit return eine Zahl zurück gibt, die nicht 0 ist, wird EEPMain() immer wieder aufgerufen. Damit entspricht die Funktion EEPMain() einem ständig kreisenden Schaltauto. Und was immer in der Funktion EEPMain() drin steht, wird bei jedem Durchlauf erneut ausgeführt.
    Bis man mit return den Wert 0 zurück gibt. Dann ist Schluss mit Lua :D



    Ich empfehle einen sehr vorsichtigen Umgang mit der Funktion EEPMain(). Mit Schaltautos haben wir uns angewöhnt durch ständiges Kreisen immer wieder zu prüfen, ob zum Beispiel ein Gleis frei geworden ist. Mit Lua ist das Unfug und man muss die bisherigen Taktiken überdenken.
    Nehmen wir den typischen Fall, dass ein Zug warten muss weil alle Gleise besetzt sind. Dann kann sich doch an dieser Situation nur etwas ändern, wenn ein anderer Zug ein Gleis verlässt. Ich muss also nicht alle Nase lang fragen, ob nun endlich ein Gleis frei geworden ist. Ich muss mir nur merken, dass ein Zug wartet. Der ausfahrende Zug kann dann die Funktion aufrufen, die dem wartenden Zug die Einfahrt ermöglicht.



    Wenn man mit einer neuen Anlage beginnt und sich das Skript anschaut, welches automatisch erstellt wird, dann steht in der EEPMain() Funktion die Zeile:
    I=I+1
    Eine Variable mit dem unleserlichen Namen I wird also bei jedem Durchlauf um Eins rauf gezählt.


    Damit das klappen kann muss diese Variable außerhalb der Funktion definiert werden. Denn eine Variable, die nur innerhalb einer Funktion definiert ist, wird bei jedem neuen Funktionsaufruf zurück gesetzt. Deshalb steht ganz am Anfang des Skripts die Zeile
    I=0


    Es ist sehr nützlich die Durchläufe von EEPMain() zu zählen. Weil man so einen Takt bekommt, an dem man sich orientieren kann. Man kann zum Beispiel dafür sorgen, dass eine Ampel immer dann umgeschaltet wird, wenn EEPMain() 50 Mal durchlaufen wurde.


    Ich möchte deshalb anraten dieser Variablen einen lesbaren Namen zu geben.
    Schreibt an den Anfang eures Skripts statt
    I=0
    lieber
    TaktGeber=0


    und statt
    I=I+1
    lieber
    TatktGeber=TaktGeber+1


    Natürlich muss dann auch das I in der Zeile
    print("Counter: ",I)
    ausgetauscht werden. Also
    print("Counter: ", TaktGeber)



    An dem, was das Skript tut, ändert das überhaupt nichts. Es wurde nur der unleserliche Variablenname I durch den viel lesbareren Namen Taktgeber ersetzt.



    Ab dem nächsten Kapitel möchte ich zeigen, was man mit dem bisherigen Wissen in EEP anstellen kann. Dafür möchte ich einen kleinen Schattenbahnhof nutzen, den ich vor längerer Zeit in die Downloadbase gestellt hatte.
    Schattenbahnhof in der DLB


    Die Anlage enthält eine "klassische" Steuerung mit Schaltautos sowie eine PDF mit Erläuterungen. Schmeißt bitte die beiden Schaltstrecken raus und löscht auch die Zugverbände auf der Anlage. Aber lasst die KPs, welche nicht mit den Schaltstrecken verschwinden, bitte bestehen. Die werden noch benötigt.

  • In diesem Kapitel möchte ich eine erste, einfache Anwendung von Lua auf einer Anlage zeigen. Ich werde mich dabei auf den Schattenbahnhof aus der Downloadbase beziehen. Aber ebensogut könnt ihr auch eine eigene Testanlage für diesen Zweck erstellen. Nehmt nur bitte nicht eure Lieblingsanlage, weil dort sonst einiges durcheinander geraten wird. Denn was ich jetzt erkläre ist keine sinnvolle Steuerung, sondern nur eine alberne Spielerei die ein wenig mehr verdeutlicht, was bestimmte Dinge in Lua tun.


    Zunächst möchte ich eine einfache Tabelle vorstellen.


    Auf meiner Anlage stehen im Schattenbahnhof fünf Ausfahrsignale mit den Nummern 29, 30, 31, 32 und 33. Diese Nummerierung ist nicht besonders praktisch. Und bei gewachsenen Anlagen kann sie noch viel chaotischer sein.


    Um mir die Programmierung bequemer zu machen, möchte ich die Signale neu nummerieren. Und zwar als Ausfahrsignal 1 bis 5.


    Dazu erstelle ich eine Tabelle mit dem Namen AusfahrSignal. Das ist im Grunde nichts anderes als eine Variable mit mehreren Speicherplätzen. Diese Speicherplätze können entweder durchnummeriert oder mit Namen versehen werden.


    Damit die Variable AusfahrSignal mehrere Speicherplätze bekommt, muss ich sie so definieren:


    AusfahrSignal = {}


    Hinter dem Gleichheitszeichen stehen geschweifte Klammern. Die finde ich auf meiner Tastatur, indem ich die rechte AltGr Taste gedückt halte und die 7 bzw. 0 drücke.


    Eine Besonderheit bei Lua ist, dass diese Tabellen automatisch mitwachsen. Die Variable hat immer so viele Speicherplätze, wie ich benötige. Deshalb muss ich beim Anlegen einer Tabelle keine Größe vorgeben.


    Um einen dieser Speicherplätze anzusprechen, benötige ich die eckigen Klammern. Ich schreibe zum Beispiel


    AusfahrSignal[1] = 29


    um im Platz 1 der Variablen AusfahrSignal den Wert 29 abzuspeichern.


    Und mit der gleichen Methode hole ich mir auch den Inhalt aus diesem Speicherplatz


    print(AusfahrSignal[1])


    gibt jetzt den Wert 29 aus, weil ich den zuvor unter der Nummer 1 abgelegt habe.



    Wenn ich schon beim Erstellen einer Tabelle weiß, welche Informationen ich darin aufbewahren will, dann kann ich sie gleich bei der Definition in die geschweiften Klammern schreiben:


    AusfahrSignal = {29,30,31,32,33}


    Wichtig ist, dass die einzelnen Werte mit einem Komma getrennt sind, damit sie der Reihe nach den Speicherplätzen zugewiesen werden.



    Mit dieser Tabelle habe ich nun die Möglichkeit meine Ausfahrsignale als AusfahrSignal[1] bis AusfahrSignal[5] anzusprechen. Was in diesem kleinen Beispiel noch wie übertriebener Aufwand scheinen mag, das erweist sich später sehr schnell als nützlich.



    Zeit für ein kleines Experiment. Ich möchte, dass die Ausfahrsignale reihum zuerst auf Halt, dann auf Fahrt, dann wieder auf Halt schalten und so weiter.


    Zunächst lösche ich das Ausgabefenster:
    clearlog()


    dann schreibe ich etwas rein, damit ich sehen kann, dass mein Skript abgearbeitet wird.


    print("Lua winkt")


    jetzt definiere ich meine Variablen:


    TaktGeber = 0
    AusfahrSignal = {29,30,31,32,33}
    Halt = 2
    Fahrt = 1


    Weil ich die Variablen außerhalb einer Funktion definiere, sind sie überall im Skript bekannt und verfügbar. Es sind globale Variablen.


    Nun folgen die Funktionen in beliebiger Reihenfolge:


    function EEPMain()
    print("EEPMain Aufrufe: ",TaktGeber)
    gebeHandzeichen()
    TaktGeber = TaktGeber + 1
    return TaktGeber % 20
    end


    In der Hauptfunktion schreibe ich zunächst den aktuellen Wert von TaktGeber ins Ausgabefenster. Für das korrekte Arbeiten von Lua ist das nicht notwendig. Aber so kann ich in meiner Experimentierphase gut beobachten, was passiert.


    Dann rufe ich eine Funktion gebeHandzeichen() auf, die ich noch definieren muss.


    Als nächstes zähle ich zum TaktGeber 1 dazu. So kann ich zählen, wie oft die EEPMain() Funktion schon aufgerufen wurde.


    Und zuletzt gebe ich einen Wert zurück, den ich aus dem TaktGeber errechne. Dazu nutze ich den Modulo Operator. Also das % Zeichen. Beim ersten Durchlauf wird mit return der Wert 1 zurück gegeben. Denn zu diesem Zeitpunkt habe ich zum Taktgeber schon das erste Mal 1 hinzugezählt. Beim zweiten Mal gebe ich dann 2 zurück, beim dritten Mal 3 und so weiter. Und solange return in der EEPMain() Funktion eine Zahl zurück gibt die größer als 0 ist, wird die Funktion erneut aufgerufen.


    Beim zwanzigsten Mal macht der Modulo Operator aus der 20, die in der Variablen TaktGeber jetzt gespeichert ist, eine 0. Und wenn return den Wert 0 zurück gibt, dann wird EEPMain() nicht erneut aufgerufen. Mit diesem Trick verhindere ich, dass die Signale endlos umschalten.


    Zum Schluss zeigt end das Ende der Funktion.



    Nun zur Funktion, welche die Signale bewegen soll:


    function gebeHandzeichen()
    if TaktGeber%10<5 then
    EEPSetSignal(AusfahrSignal[TaktGeber%5+1],Fahrt)
    else
    EEPSetSignal(AusfahrSignal[TaktGeber%5+1],Halt)
    end
    end


    Zunächst benutze ich wieder den Modulo Operator, um aus dem Wert im TaktGeber einen anderen Wert zu errechnen. Ich möchte nämlich nur Werte zwischen 0 und 9 erhalten.


    Wenn dieser errechnete Wert kleiner als 5 ist, dann schalte ich ein Signal auf Fahrt. Andernfalls schalte ich es auf Halt.


    Diese Unterscheidung treffe ich durch den Befehl if ... then.
    zwischen den Worten if und then steht die Bedingung, die erfüllt sein muss. In diesem Fall muss der Wert TaktGeber%10 kleiner als 5 sein.
    In der nächsten Zeile steht dann, was passieren soll, wenn die Bedingung erfüllt ist. Dann folgt ein else, zu deutsch "andernfalls".
    In der darauffolgenden Zeile steht, was passieren soll wenn die Bedingung nicht erfüllt wurde. Und zuletzt muss ich den if ... then Teil mit einem end abschließen. Sonst würde auch alles, was danach im Skript steht nur dann ausgeführt, wenn die Bedingung nicht erfüllt ist.


    Die Funktion selber muss ebenfalls mit end abgeschlossen werden. Auch dann, wenn danach im Skript nichts mehr steht!



    Folgendes passiert, wenn man EEP mit diesem Skript startet:


    Beim ersten Aufruf der EEPMain() Funktion steht der TaktGeber auf 0
    In der Funktion gebeHandzeichen() wird geprüft, ob 0%10 kleiner als 5 ist. Die Bedingung ist erfüllt, also wird die nächste Zeile ausgeführt.


    In der nächsten Zeile wird mit EEPSetSignal() ein Signal geschaltet. Das Signal steht in der Tabelle AusfahrSignal. Der Platz, an dem das Signal in der Tabelle zu finden ist, steht in eckigen Klammern. Statt einer Zahl steht hier eine Berechnung. Nämlich TaktGeber%5+1. Der TaktGeber ist 0, Taktgeber%5 ist also ebenfalls 0 und wenn ich dann noch 1 hinzu rechne, dann bekomme ich 1.


    In der Tabelle steht unter AusfahrSignal[1] der Wert 29.


    Also wird Signal 29 umgeschaltet. Und zwar in die Stellung Fahrt.


    Statt der komplizierten Zeile hätt man auch einfach
    EEPSetSignal(29,1)
    schreiben können. Der Effekt wäre der gleiche.


    Weil die Bedingung zwischen if und then erfüllt war, wird der Teil nach else übersprungen. Damit ist das Ende der Funktion erreicht und es geht wieder zurück zur Funktion EEPMain(). Dort wird zu TaktGeber 1 hinzu gezählt. Dieser neue Wert, also 1, wird dann aus EEPMain() zurück gegeben. Das sorgt dafür, dass die EEPMain() Funktion kurz darauf erneut aufgerufen wird.


    Und dieses Mal ist der Wert im TaktGeber 1.
    Also ist die Bedingung in der Funktion gebeHandzeichen() noch immer wahr.
    Das Signal, welches angesprochen wird, ist das Signal, welches in AusfahrSignal[2] gespeichert wurde. Denn TaktGeber%5+1 ist 2.


    Es wird also Signal 30 auf Fahrt gestellt.
    Der Rest spielt sich dann genauso ab, wie im ersten Durchlauf.

  • In der dritten Runde wird Signal 31 auf Fahrt gestellt. Dann Signal 32 und in der fünften Runde das Signal 33.


    Beim sechsten Durchlauf ist der Wert in TaktGeber 5.


    Die Bedingung Taktgeber<5 ist also nicht mehr erfüllt. Denn Taktgeber ist nicht kleiner, sondern gleich 5. Damit wird der Teil ausgeführt, der hinter dem else steht.


    Die Stelle in der Tabelle, an der die Signalnummer steht, ist Taktgeber%5+1. Taktgeber%5 ist 0. Also steht die Signalnummer unter AusfahrSignal[1]. Das bedeutet, dass im sechsten Durchlauf das Signal 29 auf Halt gestellt wird. Im siebten Durchgang wird dann Signal 30 auf Halt gestellt. Dann Signal 31, 32 und im zehnten Durchgang das Signal 33.


    Im elften Durchlauf wird dann Signal 29 erneut auf Fahrt gestellt. Und so weiter, bis nach 20 Durchgängen die EEPMain() Funktion nicht mehr aufgerufen wird.



    Diese kleine Spielerei hat keinen praktischen Nutzen.
    Sie soll nur verdeutlichen, was Variablen, Tabellen, der Modulo Operator und die if... then Verzweigung tun.


    Da ich Wert darauf lege mit farbigen Kennzeichnungen Variablen, Befehle und Funktionen deutlich zu unterscheiden, kann ich das Skript nicht als Quellcode darstellen. Und damit habe ich auch nicht die Möglichkeit, Teile des Codes einzurücken. Das ist für die Lesbarkeit aber sehr wichtig. Deshalb ist hier das Skript noch einmal komplett als Quellcode:




    Durch Einrücken kann man gut sichtbar machen, was zusammen gehört.


    Beispielsweise alles, was zwischen
    function EEPMain()
    und dem zugehörigen
    end
    steht.


    function bildet sozusagen den Anfang des Blocks, end sein Ende. Alles, was dazwischen steht, rückt man gleich weit ein. Wie weit genau, das ist jedem selbst überlassen. Ich benutze gerne die Tab Taste dafür, weil sie bequem ist. Manche Programmierer bevorzugen vier Leerzeichen, weil die Tabulator Position nicht fest definiert ist. Im Code Block habe ich die "vier Leerzeichen" Methode verwendet.



    Im nächsten Kapitel will ich eine Variante der Einfahrkontrolle für den Schattenbahnhof vorstellen.

  • Mit meinem letzten Kapitel bin ich nicht wirklich glücklich.


    Ich wollte zu viel auf einmal und habe ein Skript geschrieben, dass ich selbst sehr hässlich finde.


    Ganz besonders stört mich, dass ich in der Funktion EEPSetSignal() berechne, welches Signal umgeschaltet wird. Das funktioniert zwar und Lua ist es völlig egal, ob ich die Berechnung im Funktionsaufruf durchführe oder zuvor. Aber den Code finde ich sehr unleserlich.


    Meines Erachtens ist es übersichtlicher, wenn ich erst die Berechnung durchführe und in einer Variablen ablege. Beispielsweise so:


    x = TaktGeber % 5 + 1
    EEPSetSignal(AusfahrSignal[x],Halt)


    Dann steht die Formel nämlich frei und ist leichter zu erkennen.


    Alternativ könnte ich in der Formel auch gleich die Nummer des Ausfahrsignals bestimmen:


    x = AusfahrSignal[TaktGeber % 5 + 1]
    EEPSetSignal(x,Halt)


    Im Ergebnis unterscheiden sich diese Varianten nicht. Es ist nur eine Stilfrage, wie man das schreibt. Aber guter Stil ist wirklich wichtig. Weil stilvolle Schreibweise einen lesbaren Code ergibt. Über Stilfragen können sich Programmierer prächtig streiten. Denn letztlich bleiben sie Geschmacksache. In schlechtem Stil geschriebener Code kann von Lua ebenso gut ausgeführt werden wie stilvoller Code. Aber die Lesbarkeit ist immens wichtig. Für einen selbst bei der Fehlersuche. Und erst recht für andere, die den Code nachvollziehen möchten.


    Ich bin selber Anfänger und mache daher gerade in Stilfragen ärgerliche Fehler. Die stören Lua zwar nicht, führen aber zu solch unleserlichen Code-Beispielen wie dem aus dem letzten Kapitel.


    Eine weitere Stilfrage wäre beispielsweise, ob ich die if … then Bedingung nutze um zu unterscheiden, wann ich meine Signale auf Halt oder Fahrt schalte. Ich könnte nämlich ebenso gut abwechselnd aus dem TaktGeber den Wert 1 oder 2 errechnen und diese Zahl in der Funktion EEPSetSignal() verwenden:


    y = math.floor(TaktGeber/5) % 2 + 1


    Wenn ich den Wert aus dem TaktGeber durch 5 teile, dann bekomme ich für die ersten Werte 0 bis 4 als Ergebnis eine Zahl, die kleiner als 1 ist. Die in Lua definierte Funktion math.floor() schneidet die Nachkommastellen einfach ab. Übrig bleibt 0. Bei den Werten 5 bis 9 erhalte ich als Ergebnis einen Wert zwischen 1 und 2. Durch math.floor() bleibt 1 übrig, weil wieder die Nachkommastelle abgeschnitten wird. Ich bekomme also immer eine ganze Zahl, die nur jedes fünfte Mal um 1 größer wird. Mit dem Modulo Operator sorge ich dafür, dass immer abwechselnd eine 0 oder 1 heraus kommt. Denn aus der 2 wird wieder 0, aus der 3 wird 1 aus der 4 wird wieder 0 und so weiter. Weil ich für das Signalbild aber die Werte 1 und 2 benötige, zähle ich zum Schluss noch 1 dazu.


    So sähe die alternative Version der Funktion gebeHandzeichen() aus:


    Code
    1. function gebeHandzeichen()
    2. x = TaktGeber % 5 + 1
    3. y = math.floor(TaktGeber/5) % 2 + 1
    4. EEPSetSignal(AusfahrSignal[x],y)
    5. end


    Meines Erachtens ist das viel übersichtlicher.



    Apropos Stil:
    Ich bin ein Freund lesbarer Namen für Variablen. Deshalb vermeide ich beispielsweise das
    I=0 und I=I+1
    aus dem Standard Skript und schreibe lieber
    TaktGeber=0 und TaktGeber=TaktGeber+1


    Trotzdem habe ich im obigen Beispiel als Variablennamen x und y verwendet. Der Grund dafür ist, dass ich diese Variablen nur für einen kurzen Moment brauche. Die sind sozusagen für den "Sofortverzehr".


    Das kann ich noch weiter verbessern, indem ich diese Variablen im Skript als lokale Variablen definiere. Denn lokale Variablen existieren nur innerhalb der Funktion, in der sie definiert werden. Anschließend werden sie wieder "vergessen". Also gelöscht. Das spart Resourcen.


    Um x und y zu lokalen Variablen zu machen, muss bei der ersten Verwendung das Schlüsselwort local vorangestellt werden. Also:
    local x = TaktGeber % 5 + 1
    und
    local y = math.floor(TaktGeber/5) % 2 + 1


    Komplett sieht das dann so aus:


    Code
    1. function gebeHandzeichen()
    2. local x = TaktGeber % 5 + 1
    3. local y = math.floor(TaktGeber/5) % 2 + 1
    4. EEPSetSignal(AusfahrSignal[x],y)
    5. end



    Weil ich das x und y gleich an Ort und Stelle benutze, kann ich auch leicht nachvollziehen, wo es herkommt, was drin steht und wie es benutzt wird. In diesem Zusammenhang ist ein einfaches x oder y für mich lesbarer als ein langer Namen.


    Bei globalen Variablen sieht es anders aus. Die benutzt man oft an ganz unterschiedlichen Stellen im Skript. Und dann wird es schwer im Kopf zu behalten, welche Variablen für welchen Zweck gedacht waren wenn man sie nur x, y oder z nennt.



    Und zum Schluss noch ein letzter Satz zur Stilfrage:
    Mit Leerzeichen kann man in Lua recht frei umgehen. Beispielsweise bei Berechnungen.
    Ich kann wahlweise
    TaktGeber=TaktGeber+1
    oder
    TaktGeber = TaktGeber + 1
    schreiben.
    Lua ist das egal.


    Und ich gestehe, dass ich Mühe habe, mich zu entscheiden. Wenn ich hier eine einzelne Berechnung erläutere, dann verwende ich gerne die zusätzlichen Leerzeichen, weil ich denke, dass es der Lesbarkeit dient. Im Code selber verzichte ich eher darauf.


    Bei den Parametern in Funktionsaufrufen gilt das selbe:
    EEPSetSignal(29,1) und EEPSetSignal(29, 1)
    sind völlig gleichwertig. In der zweiten Variante habe ich hinter dem Komma ein Leerzeichen eingefügt, um die 1 etwas abzurücken. Meines Erachtens ist so deutlicher zu erkennen, dass es sich bei 29 und 1 um zwei separate Zahlen handelt.


    Hinweis: Dieser Beitrag enthält im Tutorial mit Diskussion (Link siehe oben) einen Fehler, welcher erst in den nachfolgenden Beiträgen aufgeklärt und korrigiert wurde. Hier findet ihr direkt die korrekte Vorgehensweise.

  • Mir hat lange die Inspiration gefehlt.
    Aber heute verspüre ich Lust, die if Bedingung genauer zu studieren.


    if benutzt man, wenn etwas nur unter bestimmten Umständen passieren soll.


    Falls das Hilfssignal "Fahrt" zeigt, gib den Weg für den Zug frei.



    Die Bedingung kann aus einem komplizierten Term bestehen, deshalb muss zur Kennzeichnung am Ende das Schlüsseleort then, zu Deutsch "dann" stehen.


    In einer if Verzweigung können mehrere Anweisungen stehen. Daher weiß Lua nicht, welche der nachfolgenden Zeilen nur dann ausgeführt werden sollen, wenn die Bedingung erfüllt ist. Deshalb muss das Ende des if Blocks immer mit dem Schlüsselwort end gekennzeichnet werden. Auch dann, wenn nur eine einzige Anweisung direkt hinter das Schlüsselwort then geschrieben wird.


    Code
    1. if EEPGetSignal(hilfsSignal) == stellungFAHRT then EEPSetSignal(1, 2)
    2. end


    Außerdem ist es bei jeder if Verzweigung sinnvoll, Überlegungen anzustellen was ist, wenn die Bedingung nicht erfüllt ist.


    Falls das Hilfssignal "Fahrt" zeigt, gib den Weg für den Zug frei.
    Ansonsten merk dir, dass ein Zug wartet.


    Das englische Wort für "ansonsten" ist else


    Code
    1. if EEPGetSignal(hilfsSignal) == stellungFAHRT then
    2. -- stelleWeichen()
    3. -- oeffneSignal()
    4. else
    5. -- setzeMerker()
    6. end


    Genau genommen muss mein hilfsSignal "Halt" zeigen, wenn der Merker gesetzt werden soll.
    Ich will also nicht "ansonsten", sondern "ansonsten, falls ..."


    Dafür benutzt man statt else das elseif


    Code
    1. if EEPGetSignal(hilfsSignal) == stellungFAHRT then
    2. -- stelleWeichen()
    3. -- oeffneSignal()
    4. elseif EEPGetSignal(hilfsSignal) = stellungHALT then
    5. --setzeMerker()
    6. end


    Aber was machen wir in den Fällen, in denen das Hilfssignal weder Fahrt noch Halt zeigt?
    Den Fall gibt es nicht?
    Sehr gut! Das sollten wir ausnutzen:



    Ich schreibe also eine Fehlermeldung in das Ereignisfenster und stoppe dann die Ausführung des Lua Skripts.
    Und wenn ich die Fehlermeldung detaillierter schreibe, dann weiß ich später auch genau, wo welcher Fehler passiert ist.



    Das Schema muss man nicht zwingend befolgen. Es soll nur zeigen, wie man sich Dinge zunutze machen.


    Ich habe zudem manches in diesem Bespiel vereinfacht. Ich habe bewusst darauf verzichtet die if Verzweigung in eine Funktion einzubetten. Das muss man aber tun, wenn man diese Verzweigung im richtigen Augenblick verwenden will. Zum Beispiel dann, wenn ein Kontaktpunkt überfahren wird. Funktionen sind sozusagen das, was in Basic das GOTO ist. Eine Sprungadresse. Nur, dass Funktionen viel mehr können. Denn eine Funktion kehrt am Ende dorthin zurück, wo sie aufgerufen wurde. Und liefert wahlweise auch gleich die gewünschten Attribute mit.


    Aber das ist ein anderes Thema.

  • Lua ist eine elegante Alternative zu den "Wenn … dann"-Verknüpfungen in EEP.


    Die Vorteile:

    • Man hat alle Verknüpfungen zentral an einem Platz und nicht über die gesamte Anlage verstreut.
    • Eine Signal oder eine Weiche kann mehrere andere zugleich beeinflussen.
    • Die Wenn … dann Verknüpfungen können in Abhängigkeit von Bedingungen stehen.


    Zunächst brauchen wir in Lua das "Wenn"
    Lua muss reagieren, wenn sich eine Signal- oder eine Weichenstellung ändert.


    Die passende Funktion heißt EEPOnSignal_x() bzw. EEPOnSwitch_x()
    Man nennt diese Funktionen "Callback" Funktionen (zu deutsch "Rückruf" Funktionen).
    Das x steht hier stellvertretend für die ID, also die Signal- oder Weichennummer (ohne die führenden Nullen!). Diese Nummer ist Teil des Funktionsnamens, also nicht ein Funktionsparameter!


    EEP ruft diese Funktion selbständig auf wenn sich ein Signal bzw. eine Weiche ändert. Die Funktion muss im Skript also nicht aufgerufen werden. Aber sie muss definiert werden, damit Lua weiß, was im Fall des Funktionsaufrufs zu tun ist. Das Schlüsselwort für die Definition einer Funktion heißt function.


    Für ein Signal Nummer 0010 kann das so aussehen:

    Code
    1. function EEPOnSignal_10(meinStatus)
    2. -- tu etwas mit meinStatus
    3. end



    Wenn ein Signal oder eine Weiche die Callback Funktion aufruft, diese aber nicht im Skript definiert ist, dann meldet EEP einen Fehler. Damit das nicht ständig passiert rufen nur diejenigen Signale und Weichen eine Callback Funktion auf, die zuvor dafür registriert wurden. Die Funktion für die Registrierung der Callback Funktion heißt
    EEPRegisterSignal(x) bzw. EEPRegisterSwitch(x)


    Für Signal 0010 muss also im Skript stehen:

    Code
    1. EEPRegisterSignal(10)
    2. function EEPOnSignal_10(meinStatus)
    3. -- tu etwas mit meinStatus
    4. end


    [Blocked Image: http://up.picr.de/21317944fa.jpg]


    Jetzt kommt das "Dann"
    In die Callback Funktion schreibt man das, was im "Wenn" fall passieren soll. Zum Beispiel die Funktion, welche eine Weiche umschaltet.
    Wenn Signal 0010 ein Taster auf einem Stellpult ist, der eine Weiche Nummer 0001 bedienen soll, dann muss folgendes im Skript stehen:

    Code
    1. EEPRegisterSignal(10)
    2. function EEPOnSignal_10(meinStatus)
    3. EEPSetSwitch(1,meinStatus)
    4. end


    Das Signal 0010 (der Taster) gibt beim Umschalten per Mausklick eine 1 oder 2 aus, je nach Stellung. Diese Zahl wird in meinStatus gespeichert.
    Die Weiche Nummer 0001 wird dann in die Stellung meinStatus gebracht, was entsprechend entweder 1 oder 2 bedeuten kann.



    So, wie der Taster 0010 mit der Weiche 0001 verknüpft wurde, kann auch die Weiche mit dem Melder 0004 im Pult verknüpft werden.

    Code
    1. EEPRegisterSwitch(1)
    2. function EEPOnSwitch_1(meinStatus)
    3. EEPSetSignal(4,meinStatus)
    4. end


    Ein Klick auf die Weichenlaterne schaltet jetzt nicht nur die Weiche um, sondern auch die zugehörige Anzeige im Pult. Ein KP, der die Weiche umschaltet, löst ebenfalls die Callback Funktion aus, welche den Melder im Pult umschaltet.


    Aber eine Lua Funktion, welche die Weiche umstellt, löst die Callback Funktion im Normalfall nicht aus. Das ist auch gut so und sehr wichtig, weil man sich sonst mit Callback Funktionen sehr leicht Programmschleifen einhandeln könnte, die nicht mehr verlassen werden.


    Im vorliegenden Fall soll die Weiche aber den Melder umschalten, wenn sie per Stellpult bedient wird. Deshalb muss die Funktion EEPSetSwitch() noch um einen dritten Parameter erweitert werden. Und dieser muss den Wert 1 haben, wenn die Callback Funktion ausgeführt werden soll. (Egal, welche Nummer die Weiche selbst hat.)


    Der Taster stellt nun die Weiche um und die Weiche wiederum den Melder.
    Und wenn die Weiche direkt auf der Anlage umgestellt wird, dann stimmt der Melder ebenfalls, weil er nicht durch den Taster, sondern durch die Weiche umgeschaltet wird!


    Nun sind die Nummer vom Taster und die Nummer für die Stellung der Weiche nicht immer identisch. Dann ist ein Trick nötig, mit dem man aus einer Nummer eine andere macht. Theoretisch kann man das mit einer if-Verzweigung hinkriegen. Ist aber viel zu umständlich.


    Nehmen wird den klassischen Fall, dass ich die Werte 1 und 2 tauschen muss. Wenn der Taster auf 1 springt, dann soll die Weiche in Stellung 2 und umgekehrt. Dann muss aus dem Signalstatus 1 für die Weichenstellung eine 2 werden und umgekehrt. Das kann man mathematisch viel schneller erledigen als mit if-Verzweigungen:
    3 - 1 = 2
    3 - 2 = 1

    Code
    1. EEPRegisterSignal(10)
    2. function EEPOnSignal_10(meinStatus)
    3. EEPSetSwitch(1,3-meinStatus)
    4. end



    Wenn die Verknüpfungen komplexer sind - beispielsweise bei mehrbegriffigen Signalen, dann empfiehlt sich der Einsatz von Tabellen.
    [Blocked Image: http://up.picr.de/21317945fp.jpg]


    Im folgenden Beispiel soll ein Taster Nummer 0013 mit zwei Stellungen ein Signal Nummer 0011 mit drei möglichen Stellungen bedienen. Er soll das Signal entweder auf "Halt" (Stellung 1) oder auf "Fahrt mit 40" (Stellung 3) schalten.

    Code
    1. EEPRegisterSignal(13)
    2. function EEPOnSignal_13(meinStatus)
    3. meineZuweisung = {1,3}
    4. EEPSetSignal(1,meineZuweisung[myStatus])
    5. end


    Die Tabelle besteht aus zwei Einträgen. Der erste (die Zahl 1) steht an der ersten Position, der zweite (die Zahl 3) steht an der zweiten.
    Bei Verwendung eines Wertes aus der Tabelle schreibt man den gewünschten Tabellenplatz in eckigen Klammern hinter den Tabellennamen.
    meineZuweisung[2] steht also für den zweiten Wert, der in der Tabelle gespeichert ist. In diesem Fall eine 3.
    Der Tabellenplatz kann selbst aus einer Variablen stammen. In unserem Beispiel ist das die Variable meinStatus, welche den Signalstatus aufnimmt.


    Von Benny (BH2) habe ich gelernt, dass man Tabellen auch als "implizite Konstanten" verwenden kann.
    ( Den Begriff dazu hat Gonz mir beigebracht, mir aber versichert, dass das Prinzip auch dann funktioniert, wenn man diesen Begriff nicht kennt ;) )


    Folgendes ist damit gemeint. Statt die Tabelle zunächst unter einem eigenen Namen abzuspeichern, schreibt man sie dort, wo man sie benötigt, einfach in runden Klammern vor den Index.

    Code
    1. EEPRegisterSignal(13)
    2. function EEPOnSignal_13(meinStatus)
    3. EEPSetSignal(11,({1,3})[myStatus])
    4. end


    Bitte beachten: Die geschweiften Klammern erzeugen die Tabelle. Die runden Klammern drumherum machen sie zur Konstanten für den Index, der dahinter in eckigen Klammern steht.


    Für den zugehörigen Melder brauche ich zwei Stellungen passend zu den drei Signalbegriffen des Signals. Denn der Melder kann zwar auch HP2 anzeigen (was ich persönlich sehr praktisch finde), aber realistischer ist es, wenn er nur "Halt" oder "Fahrt" anzeigt.



    Um es perfekt zu machen müsste man nun noch die Tasten entprellen. Aber das hebe ich mir für das nächste Kapitel auf.


    Korrektur - bitte vorheriges Posting löschen! erledigt

    "what's the downside to eating a clock?"

    - Joel -

    The post was edited 1 time, last by icke ().

  • Probehalber habe ich den Inhalt des letzten Kapitels "verfilmt".




    Link zu Youtube



    Link zu Youtube


    Ich habe im Video bewusst auf externe Hilfsmittel wie Notepad++ verzichtet.
    Und wie jede andere "Buchverfilmung", so weicht auch diese inhaltlich etwas von der Vorlage ab ;)


    Ich bin neugierig, ob die Videos nützlich sind. Ich kann mir nämlich vorstellen, dass manche Sachverhalte im bewegten und kommentierten Bild verständlicher sind als in geschriebenen Tutorials.