Inhaltsverzeichnis
Programmablauf
Innerhalb des Hauptprogramms werden die Programmzeilen Schritt für Schritt abgearbeitet. Das geschieht rein hierarchisch.
Der folgende Code blendet bei Tanken Einfahrt ein Symbol ein, wartet 1 sek. und gibt dann den Text Sensoren Tanken Einfahrt überfahren
als der Sprachansage aus.
... begin cpSymbolEvent('Überfahrt TankenEinfahrt'); cpSleep(1000); cpSpeech('Sensoren TankenEinfahrt überfahren'); end.
Hier läuft es genau anders herum. Zuerst Sprachausgabe, dann warten, dann Symbol einblenden.
... begin cpSpeech('Sensoren TankenEinfahrt überfahren'); cpSleep(1000); cpSymbolEvent('Überfahrt TankenEinfahrt'); end.
Schleifen
Nicht alle Aufgaben lassen sich durch derart strikte Abläufe lösen. Manchmal muss ein bestimmter Code-Teil wiederholt werden, bis ein bestimmtes Ereignis eintritt.
Pascal Script bietet für diese Szenarien drei Optionen, von denen jede andere Lösungsansätze umfasst.
While
Die Befehlszeile lautet komplett while Bedingung do. Der darauf folgende Befehl wird so lange wiederholt, bis die Bedingung zutrifft. Sollen mehrere Befehle nacheinander wiederholt werden, müssen diese in einen begin … end;
Block eingeschlossen werden. Dieser Block weist den Compiler an, alle Befehle als zur while-Bedingung zugehörig zu betrachten.
var runde : Integer; i : Integer; begin runde := 10; i := 0; while i < 5 do begin runde := runde + 1; Inc(i); end; cpShowMessage('Wert von Runde: ' + IntToStr(runde)); end.
Die Variable Runde
hat am Ende den Wert 15. Warum ist das so? Der Durchlauf der Schleife beginnt bei Wert i = 0 und geht bis i = 4. Das sind 5 Durchläufe und entsprechend werden +5 zum Ausgangswert der Variablen runde
addiert.
Wichtig ist bei der While-Schleife immer, die Bedingung für den Ausstieg im Auge zu behalten. Tritt diese nicht ein, läuft der Code unendlich weiter. Der einzige Ausweg führt dann über den harten Ausstieg durch das Abschießen der gesamten Rennbahnzeitmessung über den Taskmanager von Windows.
Repeat
Prinzipiell funktioniert die Repeat-Schleife genauso wie die While-Schleife. Wo ist dann der Unterschied? Schauen wir uns dazu direkt das Beispiel an:
var runde : Integer; i : Integer; begin runde := 10; i := 0; repeat runde := runde + 1; Inc(i); until i < 5; cpShowMessage('Wert von Runde: ' + IntToStr(runde)); end.
Auf den ersten Blick sieht doch alles ähnlich aus, oder? Trotzdem fehlen beim Wert für die Variable Runde
4 Durchläufe. Damit sind wir beim Thema.
Bei der While-Schleife steht die zu prüfende Bedingung vor dem zu durchlaufenden Code. Erst wenn, bzw. solange die Bedingung zutrifft, wird die Schleife immer wieder durchlaufen.
In der Repeat-Schleife steht die Bedingung am Ende. Sie wird also erst geprüft, nachdem die Codezeilen einmal durchlaufen wurden. Das passiert hier im Beispiel genau 1x. Dann stellt die Bedingungsprüfung fest, dass diese erfüllt ist. Der Wert von i
ist kleiner als 5!
Deshalb gibt es zwei wichtige Dinge zu beachten:
- Die zu prüfende Bedingung muss anders formuliert sein.
- Der Code innerhalb der Repeat-Schleife wird, im Gegensatz zur While-Schleife, mindestens 1x durchlaufen.
For
Die While- und die Repeat-Schleife sind mehr für eine unbestimmte Anzahl von Schleifendurchläufen ausgelegt. Natürlich lässt sich auch dort die genaue Anzahl festlegen. Das ist z.B. in dem gewählten Beispiel so. Allerdings wäre denkbar, dass die Variable i
durch Vorgänge im Code vorzeitig einen Wert annimmt, der die Schleife zum Abbruch bringt, weil die gerpüfte Bedingung erfüllt ist.
Dem gegenüber gibt es die For-Schleife, die für die Ausführung mit einer festgelegten Anzahl von Durchläufen gedacht ist.
var runde : Integer; i : Integer; begin runde := 10; for i := 0 to 5 do begin runde := runde + 1; end; cpShowMessage('Wert von Runde: ' + IntToStr(runde)); end.
Der Wert ist letztlich '16', weil der Schleifendurchlauf mit dem Wert '0' für die Variable 'i' beginnt. Damit gibt es insgesamt 6 Durchläufe.
Es geht bei der For-Schleife auch anders herum. Man beginnt man bei einem höheren Wert und zählt herunter. Das zeigt das nächste Beispiel.
var runde : Integer; i : Integer; begin runde := 10; for i := 10 downto 1 do begin runde := runde - 1; end; cpShowMessage('Wert von Runde: ' + IntToStr(runde)); end.
Der Wert ist der Variablen Runde ist '0', da die Anzahl der Durchläufe mit 10 exakt dem Startwert der Variablen entspricht.
Fallunterscheidungen
In vielen Fällen reagiert ein Programm auf bestimmte Werte, die auf festen Vorgaben, Berechnungsergebnissen oder anderen Ereignissen beruhen. Hier muss von Fall zu Fall unterschieden und der passende Code ausgeführt werden.
Dazu bietet Pascal Script zwei Wege an.
If ... then ... else
Dieses Konstrukt dürfte wohl die bekannteste Form sein, innerhalb eines Programms auf unterschiedlichste Bedigungen zu reagieren.
In Kurzform Führe Befehlsblock a aus, wenn die Bedingung zutrifft
oder, falls das nicht der Fall ist, führe Befehlsblock b aus
.
Im folgenden Beispiel wird geprüft, ob der Listenplatz im StartCenter (slot) und die Regler-ID übereinstimmen. Abhängig vom Ergebnis des Vergleichs wird eine Meldung ausgegeben.
var slot : Integer; id: Integer; begin slot := Cockpit.Slot; Cockpit.Slot := slot; id := Cockpit.SlotID; if id = slot then // Übereinstimmung cpShowMessage('Regler-ID und Slot-Index sind identisch') else // Keine Übereinstimmung cpShowMessage('Regler-ID und Slot-Index sind unterschiedlich'); end.
If-Abfragen lassen sich beliebig erweitern. Das ist jedoch bei zunehmender Anzahl der Bedingungen nachteilig für die Performances des Codes.
var slot : Integer; position : Integer; sFahrer : String; begin slot := Cockpit.Slot; Cockpit.Slot := slot; sFahrer := Cockpit.FahrerName; position := Cockpit.Position; // Mehrere if-Bedingungen (else-if) if (sFahrer <> 'Ralph525') AND (position > 2) then // Meldung ausgeben cpShowMessage('Kopf hoch. Du schaffst noch P2.') else if (sFahrer = 'slot-xtreme') OR (sFahrer = 'Fieser-Kardinal') then cpShowMessage('Prima. Du wirst sicher Erster.') else if (sFahrer = 'Ralph525') AND (position = 3) then cpShowMessage('So soll das sein.') else // Meldung, falls keie andere Bedingung zutrifft cpShowMessage('Egal. Hauptsache Spass.'); end.
Wichtig: Vor sämtlichen else if
bzw. am Ende der Meldungsbefehlszeile steht kein Semikolon als Befehlsabschluss. Der Befehl gilt erst mit dem letzten else
abgeschlossen. Hat man Befehlsblöcke pro Bedingung und arbeitet mit begin … end
, sieht das so aus:
... if sFahrer = 'slot-xtreme' then begin // sFahrer hat gewonnen if position = 1 then begin // Meldung und Symbol anzeigen, Sound abspielen cpShowMessage('Spitze. Du hast gewonnen.'); cpSound('Applaus.mp3'); cpAddOnSymbolEvent('Pokal', 1, true); end else cpShowMessage('Schade. Nächstes mal klappt's sicher.'); end; ...
Nach dem end
ist kein Befehlsabschluss in Form des Semikolons vorhanden, nach den Zeilen innerhalb des Blocks schon!
Ein weiterer Aspekt ist die verschachtelte Schreibweise beider if-Abfragen. Tatsächlich ließe sich dieser Block auch so schreiben:
... if (sFahrer = 'slot-xtreme') AND (position = 1) then begin ... end else ...;
Teilweise ist es Geschmacksache, ob man die eine oder andere Notation verwendet, teils wird es in verschachtelter Form einfacher, den Code nachzuvollziehen. Ich bevorzuge die verschachtelte Variante.
Case ... of
Prinzipiell funktioniert dieser Befehl ähnlich wie if … then … else
. Ich persönlich finde ihn allerdings schöner, sowohl was die Lesbarkeit im Code betrifft, als auch in seiner Form beim programmieren.
Bei längeren Fallunterscheidungen ist er darüberhinaus performanter als die if
-Verschachtelungen. Im Internet ist die Rede von 5 Fallunterscheidungen und mehr. Er ist jedoch nicht langsamer als if … then … else
, wenn es um weniger Varianten geht.
Codebeispiele
{StartZiel Event} {Pascal Script Engine} var slot : Integer; id : Integer; position : Integer; sFahrer : String; begin slot := Cockpit.Slot; Cockpit.Slot := slot; id := Cockpit.SlotID; position := Cockpit.Position; sFahrer := Cockpit.FahrerName; case position of 1,2: cpShowMessage('Fahrer: ' + sFahrer + ' - ID ' + IntToStr(id) ); 3 : begin if sFahrer <> 'Ralph525' then cpShowMessage('Nicht zulässiges Ergebnis:') else cpShowMessage('Du bist immerhin auf dem Podium, ' + sFahrer); end; 4..6: cpShowMessage('Wer bekommt die rote Laterne?'); else cpShowMessage('Es gibt nur 6 Rennteilnehmer'); end; end.
Dieses Beispiel zeigt bei Überfahrt von Start/Ziel eine Meldung abhängig Platzierung eines von 6 Fahrern als Meldung an. Wird Platz 7 oder 8 erkannt, führt das zur Anzeige einer Meldung, dass nur 6 Teilnehmer zugelassen sind.
Bei den Plätzen 1 und 2 zeigt die Meldung den Namen des Piloten und die zugehörige Regler-ID.
Position 3 bekommt einen Codeblock, der auf den Fahrernamen reagiert.
Die Positionen 4 bis 6 werden mit der gleichen Meldung motiviert, sich ein wenig ins Zeug zu legen, um die Ankunft auf dem letzten Platz abzuwenden.
Es geht jedoch weniger um die Inhalte, als vielmehr um die Möglichkeiten. Mehrere nicht zusammenhängende Werte lassen sich mit Trennung per Komma einem Fall zuordnen (1,2,6). Zu beachten ist nur, dass die abgefragen Werte dem Variablen-Typ entsprechen. Einer Abfrage von Integer-Variablen lassen sich keine Zeichenfolgen „unterschieben“. Ausnahme ein String „26“, der sich mit der Funktion StrToInt() in einen Integerwert umwanddeln lässt (StrToInt('26') = 26).
Gibt es einen zusammenhängenden Wertebereich wird dieser mit zwei Punkten zwischen Anfangs- und Endwert beschrieben. Das findet sich bei den Arrays wieder. Grob gesagt wird hier ein Werte-Array übergeben (1..100), etc.
Auch einzelne Werte sind möglich und ebenfalls lässt sich eine Regel else
für alle Werte festlegen, die nicht gesondert aufgeführt sind.
Wie alle Befehle muss case … of
mit einem end;
abgeschlossen werden.
Gut zu erkennen ist auch, dass für den Fall position = 3
genauso gut ein Befehlsblock für die weitere Verarbeitung möglich ist.
Zurück zum letzten Beispiel aus dem if … then … else
Teil.
var slot : Integer; position : Integer; sFahrer : String; begin slot := Cockpit.Slot; Cockpit.Slot := slot; position := Cockpit.Position; sFahrer := Cockpit.FahrerName; // Meldungen für einen bestimmten Fahrer if sFahrer = 'slot-xtreme' then begin // Meldung abhängig von der Platzierung case position of 1: cpShowMessage('Spitze, ' + sFahrer + ' Du führst.'); 2: cpShowMessage(sFahrer + ', Platz 2. Du kannst noch gewinnen.'); 3: cpShowMessage('Dieser Platz ist für Ralph525 reserviert.'); 4: cpShowMessage('Reicht es heute nicht fürs Podium, ' + sFahrer + '?'); 5: cpShowMessage('So schlecht warst Du lange nicht mehr, ' + sFahrer); 6: cpShowMessage(sFahrer + ': Unglaublich. Erbärmlich. Ohne Worte.'); end; end; end.
Dieses Beispiel mit if … then … else
sähe so aus:
var slot : Integer; position : Integer; sFahrer : String; begin slot := Cockpit.Slot; Cockpit.Slot := slot; position := Cockpit.Position; sFahrer := Cockpit.FahrerName; // Meldungen für einen bestimmten Fahrer if sFahrer = 'slot-xtreme' then begin // Meldung abhängig von der Platzierung if position = 1 then cpShowMessage('Spitze, ' + sFahrer + ' Du führst.') else if position = 2 then cpShowMessage(sFahrer + ', Platz 2. Du kannst noch gewinnen.') else if position = 3 then cpShowMessage('Dieser Platz ist für Ralph525 reserviert.') else if position = 4 then cpShowMessage('Reicht es heute nicht fürs Podium, ' + sFahrer + '?') else if position = 5 then cpShowMessage('So schlecht warst Du lange nicht mehr, ' + sFahrer) else if position = 6 then cpShowMessage(sFahrer + ': Unglaublich. Erbärmlich. Ohne Worte.'); end; end.
Die Entscheidung, welche Version für euch die „schönere“ und einfachere ist, liegt bei jedem persönlich. Für mich ist es case … of
, weil ich nicht überlegen muss, ob ein Semikolon ans Ende der Zeile gehört oder nicht. Zudem finde ich die case … of
Zeilen besser lesbar und würde ihnen jederzeit den Vorzug vor if … then … else
geben.