Kapitel 7: Flusskontrolle

7.1 Wiederholung
Variablen koennen ihre Werte durch verschiedene Ausdruecke der Zuweisung wie
= oder += aendern. Ausserdem koennen sie durch andere Ausdruecke modifiziert
werden. Derzeit koennen wir nur lineare Funktionen schreiben, die inetwa so
aussehen:

int fiese_ausgabe(int x)
{
 x--;
 write("Wir haben x auf den Wert "+x+" gesetzt.\n");
 return x;
}

Was diese Funktion macht, sollte Dir voellig klar sein und wie man sie
schreibt, auch.
Was aber, wenn man nun x nur dann ausgeben wollte, wenn x den Wert 1 hat?
Oder Du x solange ausgeben willst, bis x=1 gilt?
LPC nutzt dieselbe Flusskontrolle wie C oder C++.

7.2 LPC Flusskontrollausdruecke
In LPC gibt es folgende Ausdruecke zur Flusskontrolle:

if(ausdruck) anweisung;

if(ausdruck) anweisung
else anweisung;

if(ausdruck) anweisung
else if(ausdruck) anweisung
else anweisung;

while(ausdruck) anweisung;

do { anweisung; } while(ausdruck);

switch(ausdruck) { case (ausdruck): anweisung; break; default: anweisung; }

Bevor wir diese einzelnen Ausdruecke durchsprechen, wollen wir kurz klarstellen,
was 'ausdruck' und was 'anweisung' ist.
Ein 'ausdruck' ist alles mit einem Wert wie eine Variable, ein Vergleich oder
eine Zuweisung.
Eine 'anweisung' ist ein Funktionsaufruf, eine Wertzuweisung etc.
Ausserdem kommen wir noch einmal (wie versprochen) auf die logischen
Operatoren aus dem letzten Kapitel zurueck.
Diese Operatoren geben einen Wert von 0 zurueck, wenn der Ausdruck logisch
falsch ist, ansonsten einen Wert, der nicht 0 ist.
Wenn wir annehmen, dass als Wahrheitswert, wenn es wahr ist, 1 gesetzt wird,
gilt:
(1 && 1) value: 1   (1 and 1) UND
(1 && 0) value: 0   (1 and 0) UND
(1 || 0) value: 1   (1 or 0)  ODER
(1 == 1) value: 1   (1 is equal to 1) IST GLEICH
(1 != 1) value: 0   (1 is not equal to 1) IST UNGLEICH
(!1) 	 value: 0   (not 1) IST NICHT

Dabei ist value der return-Wert.

Dabei gilt, wenn der &&-Operator eingesetzt wird, der ja wie im letzten Kapitel
gelernt, linksbuendig ist, und der linke Wert bereits 0 ist, wird der rechte
Wert nicht mehr abgearbeitet, da das Ergebnis automatisch 0 ist.
Dasselbe gilt fuer ||, wo sobald das erste Ergebnis 1 ist, jedes weitere
Ergebnis nicht mehr getestet wird.

7.3 if()
if() ist eine Erweiterung des ?-Operators.
Schauen wir einfach mal in ein klassisches Beispiel:

void reset()
{
 int x;
 ::reset();
 x=random(10);
 if(x>5) write(x+"\n");
}

Diese Funktion, die in jedem Objekt vorhanden ist, wird hier ueberschrieben.
Sie wird regelmaessig aufgerufen, um 'aufzuraeumen'.
Dabei werden, falls in dem Objekt andere Objekte vorgehalten werden sollen
(zB NPC in Raeumen), und die nicht mehr vorhanden sind, diese Objekte neu
erstellt.
random() liefert eine Zufallszahl zwischen 0 und dem uebergebenen Wert -1.
Das einzig Neue in dieser Funktion ist das if(x>5)
Dort wird ueberprueft, ob der Wert in den Klammern wahr ist.
Ist das der Fall, wird der Befehl hinter dem Funktionsaufruf ausgefuehrt.
Optional ist ein else-Zweig, wo der Befehl steht, der im unwahr-Fall
ausgefuehrt wird.
Sollten mehrere Anweisungen oder Befehle ausgefuehrt werden, muessen die
Bloecke mit geschweiften Klammern eingefasst werden.
Beispiel:
if(x>5)
  {
   write(x+"\n");
   x=-x;
  }
else
 {
  write("Das x ist viel zu klein.\n");
  x=3*x+y;
 }
Ist das x groesser als 5 wird x ausgegeben und danach negiert.
Ansonsten wird ausgegeben, dass x zu klein sei, und mit 3 multipliziert und um
y kumuliert.
Sollen keine alternativen Befehle ausgefuehrt werden, kann der else-Zweig auch
weggelassen werden.
Das kann bei aufwaendigen if-Verschachtelungen, wie etwa Folgender,

if(name=="gralkor")
   write("Du bist Gralkor\n");
else if(name=="querolin")
        write("Du bist Querolin.\n");
      else if(name=="serii")
	      write("Du bist Serii.\n");
write("Du bist nichts.\n");

sehr schnell zu Fehlern fuehren.
Deshalb gibt es die Vereinbarung, dass der else-Zweig immer zu dem if gehoert,
das noch keinen else-Zweig hat. Und zwar von innen nach aussen.
Aufheben kann man diese Zuweisungen durch Klammerung.
Obiges Beispiel waere aber effektiver mit der switch-Anweisung zu loesen.
Switch hat allerdings ein Problem:
Der uebergebene Wert fuer den Schalter (switch) muss entweder ein char-Wert
(auch ein String ist ein char-Wert) oder vom Typ int sein.
Ein Beispiel fuer switch:

switch(name)
      {
       case "gralkor":
       write("Du bist der Oberorkbossmeister.\n");
       break;
       case "serii":
       write("Du bist ein wirklich boeser Daemon!\n");
       break;
       case "querolin":
       write("Du bist der liebenswuerdige Gott.\n");
       break;
       default:
       write("Was bist Du denn?\n");
       break;
      }

Im Falle, dass name=="gralkor" waere, wuerde ausgegeben ...
Damit entspricht dieses uebersichtlichere Codegebilde inetwa dem if()-Beispiel
von eben.
Die break-Anweisung befiehlt sowohl switch als auch Schleifen (s. spaeter),
dass die weitere Ueberpruefung abgebrochen werden soll.
Hier verlaesst man nach einem positiven Ergebnis die Entscheidung.
Der default-Zweig ist immer wahr. Damit sollte das immer ganz unten am Ende
des switch() stehen, und vorher bei wahr mit break herausgesprungen werden.

7.4 while() und do {} while()

Diese beiden Schleifen dienen dazu, bestimmte Befehle solange auszufuehren,
bis eine Ausdruck wahr wird.
Dabei ist die while()-Schleife kopfgesteuert, was bedeutet, dass sie BEVOR
jeglicher Befehl ausgefuehrt wird, den Ausdruck bewertet.
Das hat zur Konsequenz dass sie eventuell niemals durchlaufen werden wird.
Im Gegensatz dazu ist die do {} while()-Schleife fussgesteuert, was bedeutet,
dass sie auf jeden Fall EINMAL durchlaufen wird. Wann man welche Schleife
einsetzt, ist nur durch diese Idee bedingt.
Wenn man beispielsweise einen Spieler aergern will, und ihm levelabhaengig
einfach Lebenspunkte stehlen will, wuerde man das so machen:

int x;
x=this_player()->QueryProp(P_LEVEL);
while(x>10)
     {
      this_player()->do_damage(random(x));
      x--;
     }

Damit waeren natuerlich Spieler, die einen Level von 10 und weniger haben,
gut raus, da sie nie in den 'Genuss' der Schleife kommen.
Was die einzelnen Funktionen machen, die noch nicht erklaert wurden, kann ich
nur wieder auf die man-Pages verweisen.
Dasselbe als do {} while()-Schleife, mit dem Effekt, dass sie min. 1x
durchlaufen wird, und somit kein sogenannter 'Welpenschutz' existiert.

int x;
x=this_player()->QueryProp(P_LEVEL)
do
{
 this_player()->do_damage(random(x));
 x--;
}
while(x>10);

Die Unterschiede sollten deutlich sein.

7.5 for()-Schleife
Die for()-Schleife ist eine spezielle Version einer while()-Schleife.
Es handelt sich um eine sogenannte Zaehlschleife, also eine Schleife, die
definierte Durchlaeufe hat.

Das obige Beispiel waere mit einer for()-Schleife effizienter zu loesen.
Es soll aber zeigen, dass es letztlich egal ist, welche Art von Schleife man
einsetzt.

int i,x;
x=this_player()->QueryProp(P_LEVEL)
for(i=x;x>10;x--)
   {
    this_player()->do_damage(random(i));
   }

Eine Besonderheit beim Schleifendurchlauf ist zu beachten:
Beim ersten Durchlauf wird das erste Argument durchgefuehrt, mit dem zweiten
Argument verglichen und dann modifiziert.
Sollte das zweite Argument unwahr sein, wird die Schleife nicht betreten.
Die Konsequenzen, die auftauchen sind:
das letzte Argument ist immer ausgefuehrt.
Man kann auch Argumente weglassen, jedoch sind die Semikolon zu setzen.
Inwieweit das Sinn macht, muss JEDER selbst wissen, das das zu Endlosschleifen
fuehren kann.

7.6 Aendern des Flusses
Die folgenden Anweisungen dienen zur Aenderung des Flusses der oben
beschriebenen Flusskontrollanweisungen:
return continue	break

Zu allererst zum return:
Wie bereits bekannt sein sollte, dient die return-Anweisung dazu, um den
Durchlauf von Funktionen zu beenden und der aufrufenden Funktion Werte zu
uebergeben.
Sollte der Typ der Funktion nicht void sein, muss der Anweisung ein Wert
folgen.
Eine typische Verwendung sieht man hier:

int absolutwert(int x)
{
 if(x>-1) return x;
 else return -x;
}
Damit wird immer ein int-Wert, der nichtnegativ ist, uebergeben.

continue wird haeufig in Schleifen verwendet.
Damit wird eine Ausfuehrung des aktuellen Schleifendurchlaufes beendet und
erneut in die Funktion eingestiegen. Das kann dazu verwendet werden, um
beispielsweise eine Nulldivision zu verhindern:

x=4
while(x>-5)
{
 x--;
 if(!x) continue;
 write((100/x)+"\n");
}
write("Fertig.\n");

Das sollte Ausgaben produzieren:
33
50
100
-100
-50
-33
-25
Fertig.

break Diese Anweisung dient dazu, sofort zum Ende der Umgebung, die eine
Schleife oder ein switch sein kann, zu springen.
Wenn wir im obigen Beispiel anstelle von continue ein break genommen haetten,
waere folgende Ausgabe produziert worden:
33
50
100
Fertig.

continue-Anweisungen werden zumeist in Schleifen verwendet, waehrend break
am Haeufigsten in switch()-Anweisungen Anwendung findet.
Siehe dazu das Beispiel zum switch().

7.7 Zusammenfassung des Kapitels
Dieses Kapitel hat viel Information beinhaltet, aber es war alles sehr wichtig.
Es sollte inzwischen klar sein, wofuer man if(), for(), while(), do{}while()
und natuerlich auch switch() verwenden kann und sollte. Auch ist nun bekannt,
wie man kontrolliert solche Sachen effektiv verlassen kann, indem man return,
break und continue benutzt. Du weisst, dass man lieber ein switch() einsetzt,
anstatt lange if()else-Wuesten zu erschaffen. Du weisst, wie man Funktionen
in anderen Objekten aufruft. Das wird noch einmal spaeter vertieft.
Mithilfe der man-Pages bist Du inzwischen in der Lage, komplexe Gegenden und
interessante NPC zu programmieren.
mud.tamedhon.de:4711 ON
mud.tamedhon.de:4712 (TLS) ON
mud.tamedhon.de:4713 (Webclient) ON