6.1 Was sind Kontrollstrukturen?
Normalerweise wird Code von oben nach unten ausgeführt — Zeile für Zeile.
Das ist die einfachste Form eines Programms: Schritt 1, dann Schritt 2, dann Schritt 3.
Aber so ein „immer-gleich-ablaufendes“ Programm ist langweilig: Es kann auf nichts
reagieren, nichts wiederholen, nicht auf unterschiedliche Situationen unterschiedlich
antworten.
Kontrollstrukturen sind die Werkzeuge, mit denen du diesen
starren Ablauf veränderst. Sie sind das, was Programmieren erst lebendig macht.
Ohne sie könnte ein Item zwar Text ausgeben, aber nicht entscheiden, ob ein
Spieler Level 10 hat oder nicht — es würde immer denselben Text liefern.
Du kannst damit:
- Entscheidungen treffen —
if, else:
„Wenn der Spieler Level 10 hat, dann lass ihn ein.“
- Code mehrfach ausführen —
while, for,
foreach: „Für jeden Spieler im Raum: gib einen Text aus.“
- Unterschiedliche Fälle behandeln —
switch:
„Je nach Würfelwurf passiert etwas anderes.“
- Schleifen gezielt verlassen —
break,
continue: „Suche, bis du den richtigen Spieler findest, dann hör auf.“
- Fehler abfangen —
catch:
„Versuche das, aber crashe nicht, falls es schiefgeht.“
Ohne Kontrollstrukturen wäre LPC extrem eingeschränkt — eigentlich überhaupt
nicht zum Programmieren brauchbar. Sie sind das Rückgrat jeder Spielmechanik
und jeder Reaktion auf Spieleraktionen.
6.2 Die if-Anweisung – Entscheidungen treffen
Die if-Anweisung ist die einfachste und häufigste Kontrollstruktur.
Sie prüft eine Bedingung — also einen Ausdruck, der entweder „wahr“ oder „falsch“
ist — und führt einen Codeblock nur dann aus, wenn die Bedingung wahr ist.
if (x > 10)
{
write("x ist größer als 10.\n");
}
Lies das laut: „Wenn x größer als 10 ist, dann gib den Text aus.“
Wenn x 5 ist, passiert nichts. Wenn x 11 ist, passiert
die Ausgabe.
Wahr und Falsch in LPC
Anders als manche Sprachen hat LPC keinen separaten bool-Typ.
Stattdessen gilt eine einfache Regel:
0 bedeutet falsch
- Jeder andere Wert bedeutet wahr
Das gilt für alle Typen: Eine Zahl ungleich 0 ist „wahr“, ein nicht-leerer String
ist „wahr“, ein vorhandenes Objekt ist „wahr“. Ein zerstörtes Objekt zeigt sich als
0 und ist damit „falsch“. Daher der typische Test:
if (pl) {
// pl ist nicht 0, also haben wir ein Objekt
write(pl->Name(WER) + "\n");
}
if (!pl) {
// pl ist 0
write("Niemand da.\n");
}
Bedingungen können beliebig kompliziert sein
if (this_player()->QueryProp(P_LEVEL) >= 10) // einfacher Vergleich
if (hp > 0 && hp < 50) // mit UND
if (race == "Elf" || race == "Mensch") // mit ODER
if (!present("schluessel", this_player())) // mit Negation
6.3 if ohne geschweifte Klammern
Besteht der Codeblock nur aus einer einzigen Anweisung, kannst du die geschweiften
Klammern weglassen. Das ist syntaktisch erlaubt und wird in vielen LPC-Beispielen
so geschrieben:
if (hp <= 0)
die();
// Ist äquivalent zu:
if (hp <= 0)
{
die();
}
Achtung: Die Falle der weggelassenen Klammern
Wenn du später eine zweite Zeile hinzufügst, gehört sie nicht automatisch
zum if-Block — auch wenn die Einrückung das suggeriert. Klassischer Fehler:
if (hp <= 0)
die();
log_file("debug", "Spieler ist gestorben\n");
// Die log_file-Zeile wird IMMER ausgeführt, egal ob hp ≤ 0 oder nicht!
Mit Klammern wäre das eindeutig:
if (hp <= 0) {
die();
log_file("debug", "Spieler ist gestorben\n");
}
Anfänger-Tipp: Nutze Klammern fast immer — auch bei einzeiligen Blöcken.
Das ist eine billige Versicherung gegen einen sehr ärgerlichen Bug, der oft erst
Wochen später beim Erweitern auffällt.
6.4 if und else
Mit else definierst du, was passiert, wenn die Bedingung
nicht erfüllt ist. Damit hast du eine Weichenstellung: Genau einer der
beiden Zweige wird ausgeführt — nie beide, nie keiner.
if (level >= 10)
{
write("Du darfst eintreten.\n");
}
else
{
write("Du bist noch zu schwach.\n");
}
Im MUD-Code ist if/else einer der häufigsten Bausteine — überall, wo
ein Objekt auf eine Spieleraktion reagiert, steckt mindestens ein
if/else drin: Hat der Spieler die Quest schon gelöst? Hat er den
richtigen Schlüssel? Ist der Raum erleuchtet?
Verschachtelte if/else
Du kannst if/else auch in andere if/else-Blöcke schachteln — das
wird aber schnell unübersichtlich:
if (pl) {
if (pl->QueryProp(P_LEVEL) >= 10) {
write("Du darfst eintreten.\n");
} else {
write("Du bist noch zu schwach.\n");
}
} else {
write("Niemand da.\n");
}
Ab drei oder mehr Schachtelebenen wird das schnell unübersichtlich. Nutze dann
else if (siehe nächster Abschnitt) oder Frühausstiege mit
return.
6.5 Mehrere Bedingungen: else if
Für mehr als zwei Fälle nutzt man else if. Das ist eine Kette von
Bedingungen, die der Reihe nach geprüft werden. Sobald eine Bedingung
zutrifft, wird ihr Block ausgeführt — und der Rest der Kette übersprungen.
if (level < 5)
{
write("Anfänger.\n");
}
else if (level < 20)
{
write("Fortgeschrittener.\n");
}
else
{
write("Veteran.\n");
}
Lies das so: „Wenn Level < 5: Anfänger. Sonst, wenn Level < 20:
Fortgeschrittener. Sonst: Veteran.“ Dadurch sind die drei Fälle klar getrennt
und decken zusammen alle Möglichkeiten ab.
Die Reihenfolge ist entscheidend
Die Bedingungen werden von oben nach unten geprüft. Sobald eine zutrifft, wird der
Rest übersprungen. Das hat einen wichtigen Effekt: Wenn deine Bedingungen sich
überlappen, gewinnt die obere:
// Falsch herum geordnet:
if (level >= 1) write("Stufe 1+\n"); // matcht IMMER
else if (level >= 10) write("Stufe 10+\n"); // wird nie erreicht
else if (level >= 50) write("Stufe 50+\n"); // wird nie erreicht
// Richtig: spezifisch zuerst, allgemein zuletzt:
if (level >= 50) write("Stufe 50+\n");
else if (level >= 10) write("Stufe 10+\n");
else if (level >= 1) write("Stufe 1+\n");
Wann else if, wann switch?
Wenn du einen Wert mit vielen festen Möglichkeiten vergleichst (Strings,
Zahlen, Bereiche), ist switch oft lesbarer. Wenn du verschiedene
Bedingungen prüfst, die auf unterschiedlichen Variablen beruhen, ist
else if die richtige Wahl.
6.6 Vergleichs- und logische Operatoren
Bedingungen bauen meistens auf Vergleichen und logischen Verknüpfungen auf.
Hier ist die Liste der wichtigsten Operatoren — und ihre Bedeutung in
verständlichen Worten.
Vergleichsoperatoren
== — gleich. „Ist x gleich 10?“
!= — ungleich. „Ist x nicht 10?“
< — kleiner als
> — größer als
<= — kleiner oder gleich
>= — größer oder gleich
Klassische Anfängerfalle: = ist kein Vergleich, sondern
eine Zuweisung! Vergleiche brauchen zwei Gleichheitszeichen.
// FALSCH: Zuweisung statt Vergleich
if (x = 10) { ... } // setzt x auf 10 und prüft dann, ob 10 != 0 (also immer wahr!)
// RICHTIG: Vergleich mit ==
if (x == 10) { ... }
Logische Operatoren
Mit logischen Operatoren verbindest du mehrere Bedingungen:
&& — UND. Beide Bedingungen müssen wahr sein.
|| — ODER. Mindestens eine Bedingung muss wahr sein.
! — NICHT. Macht aus wahr falsch und umgekehrt.
if (pl && pl->QueryProp(P_LEVEL) >= 10)
{
write("Zugriff erlaubt.\n");
}
// "Wenn pl existiert UND sein Level mindestens 10 ist..."
if (race == "Elf" || race == "Mensch")
{
write("Du gehörst zur erlaubten Rassengruppe.\n");
}
// "Wenn die Rasse Elf ODER Mensch ist..."
if (!present("schluessel", this_player()))
{
write("Du hast keinen Schlüssel.\n");
}
// "Wenn KEIN Schlüssel vorhanden ist..."
Short-Circuit-Auswertung
&& und || haben einen sehr nützlichen Effekt:
Sie werten von links nach rechts aus und brechen ab, sobald das Ergebnis
feststeht. Bei A && B wird B gar nicht erst
ausgewertet, wenn A bereits falsch ist. Das nutzt man bewusst aus,
um sichere Aufrufe zu schreiben:
// Wenn pl == 0 ist, wird QueryProp(P_LEVEL) NICHT aufgerufen — kein Crash!
if (pl && pl->QueryProp(P_LEVEL) >= 10)
...
Reihenfolge: Vergleichsoperatoren binden enger als &&, das
wiederum enger als ||. Bei Unsicherheit: setze Klammern.
6.7 Die while-Schleife
Eine while-Schleife wiederholt einen Codeblock, solange
eine Bedingung wahr ist. Sie prüft die Bedingung am Anfang jedes Durchlaufs:
Wenn die Bedingung dort schon falsch ist, wird der Block überhaupt nicht ausgeführt.
int i;
i = 0;
while (i < 5)
{
write("i = " + i + "\n");
i++;
}
Lies das so: „Solange i kleiner als 5 ist, gib den Wert aus und
erhöhe i um 1.“ Die Ausgabe ist 0, 1, 2, 3, 4 — bei i = 5
ist die Bedingung falsch und die Schleife endet.
Wann ist while die richtige Wahl?
while eignet sich, wenn du nicht im Voraus weißt, wie oft
die Schleife laufen wird. Beispiele:
// Solange ich noch HP habe, kämpfe weiter:
while (this_object()->QueryProp(P_HP) > 0) {
// ...
}
// Solange ein bestimmter Spieler im Raum ist:
while (present("odin", environment(this_object()))) {
// ...
break; // sicherheitshalber!
}
Achtung: Endlosschleifen!
Wenn die Bedingung nie falsch wird, entsteht eine Endlosschleife. Das ist im MUD
doppelt gefährlich: Es blockiert nicht nur dein Objekt, sondern den
ganzen Driver — alle Spieler hängen, bis das Tick-Limit zuschlägt und einen
„too long evaluation“-Fehler wirft.
// FALSCH: i wird nie verändert — Endlosschleife!
int i = 0;
while (i < 5) {
write(i + "\n");
// i++ vergessen!
}
Faustregel: Wenn du while schreibst, frage dich sofort: „Wo ändere
ich die Bedingung, damit sie irgendwann falsch wird?“
6.8 Die for-Schleife
Die for-Schleife ist kompakter als while und wird häufig
für Zählvorgänge genutzt — also wenn du genau weißt (oder
ausrechnen kannst), wie oft die Schleife laufen soll.
int i;
for (i = 0; i < 5; i++)
{
write("i = " + i + "\n");
}
Aufbau der for-Schleife
Eine for-Schleife hat drei Teile, getrennt durch Strichpunkte:
- Initialisierung (
i = 0) — wird einmal am Anfang ausgeführt.
- Bedingung (
i < 5) — wird vor jedem Durchlauf geprüft.
- Änderung (
i++) — wird nach jedem Durchlauf ausgeführt.
Das ist im Grunde nur eine kompaktere Schreibweise einer while-Schleife:
// for(...) entspricht:
int i = 0; // Initialisierung
while (i < 5) { // Bedingung
write("i = " + i + "\n");
i++; // Änderung
}
Variable direkt deklarieren
Modern wird die Schleifenvariable oft direkt im for deklariert.
Sie lebt dann nur innerhalb der Schleife — sauberer und weniger Namens-Konflikte:
for (int i = 0; i < 5; i++) {
write("i = " + i + "\n");
}
// hier ist i nicht mehr bekannt
Klassische Verwendungsmuster
// Über ein Array iterieren:
for (int i = 0; i < sizeof(arr); i++) {
write(arr[i] + "\n");
}
// Rückwärts zählen:
for (int i = 10; i > 0; i--) {
write(i + "\n");
}
// In Zweier-Schritten:
for (int i = 0; i < 100; i += 2) {
...
}
Tipp: Für Schleifen über Arrays oder Mappings ist foreach oft eleganter
und weniger fehleranfällig (siehe Abschnitt 6.11b).
6.9 Schleifen über Inventare
Sehr typisch im Midgard MUD: Schleifen über Inventare von Objekten. Ein Spieler hat
Items, ein Container hat Inhalt, ein Raum hat NPCs und Gegenstände — überall braucht
man die „Liste aller Objekte da drin“. Dafür gibt es all_inventory(),
das ein Array zurückgibt.
object ob;
object *inv;
inv = all_inventory(this_player());
foreach (ob : inv)
{
write(object_name(ob) + "\n");
}
Lies das so: „Hol mir das Inventar des aktuellen Spielers (ein Array von Objekten),
und dann gib für jedes Objekt darin den Namen aus.“ foreach ist
besonders lesbar und ideal für Anfänger — du musst keinen Index zählen, kein
sizeof() abfragen, kein „Index out of range“ befürchten.
Typische Inventar-Aufgaben
// Wie viele Lebewesen sind im Raum?
int count = 0;
foreach(object ob : all_inventory(environment(this_player()))) {
if (living(ob)) count++;
}
write("Lebewesen im Raum: " + count + "\n");
// Suche das schwerste Objekt im Inventar:
object schwerstes;
int max_gewicht = 0;
foreach(object ob : all_inventory(this_player())) {
int g = ob->QueryProp(P_WEIGHT);
if (g > max_gewicht) {
max_gewicht = g;
schwerstes = ob;
}
}
// Drop alle Lichtquellen ab:
foreach(object ob : all_inventory(this_player())) {
if (ob->QueryProp(P_LIGHT) > 0)
ob->move(environment(this_player()), M_PUT);
}
Wenn du verschachtelte Container hast (Beutel im Inventar, Schachtel im Beutel),
gibt es deep_inventory() — das liefert auch alle indirekten Objekte
zurück. Hilfreich beim Suchen, aber teurer als all_inventory().
6.10 break und continue
Innerhalb von Schleifen gibt es zwei Spezial-Anweisungen, mit denen du den
normalen Ablauf unterbrechen kannst: break verlässt die Schleife
komplett, continue überspringt den Rest des aktuellen Durchlaufs
und macht mit dem nächsten weiter.
break — Schleife sofort verlassen
while (1) // Endlosschleife — aber mit kontrolliertem Ausstieg
{
if (x > 10)
break; // sobald x > 10 ist, Schleife verlassen
x++;
}
write("Fertig, x = " + x + "\n");
Klassischer Use-Case: Suche in einer Liste, sobald du gefunden hast, was du suchst,
brich ab — kein Sinn, weiterzusuchen.
object schwert;
foreach(object ob : all_inventory(this_player())) {
if (ob->id("schwert")) {
schwert = ob;
break; // gefunden, raus aus der Schleife
}
}
if (schwert) write("Gefunden: " + schwert->Name(WER) + "\n");
continue — diesen Durchlauf überspringen
for (i = 0; i < 10; i++)
{
if (i == 5)
continue; // 5 überspringen, aber weitermachen
write(i + "\n");
}
// Ausgabe: 0, 1, 2, 3, 4, 6, 7, 8, 9 (5 fehlt!)
continue ist nützlich, wenn du eine Bedingung hast, bei der du bestimmte
Elemente überspringen willst — etwa beim Filtern:
foreach(object ob : all_inventory(this_player())) {
if (!ob->QueryProp(P_WEIGHT)) continue; // Gewichtslose Items überspringen
if (ob->QueryProp(P_NODROP)) continue; // Nicht-fallbare überspringen
// alles andere fallen lassen:
ob->move(environment(this_player()), M_PUT);
}
Beide Anweisungen wirken nur auf die innerste Schleife. Bei verschachtelten
Schleifen brichst du nur die innere ab — die äußere läuft weiter.
6.11 Die switch-Anweisung
switch eignet sich für die Situation, wenn du einen Wert mit
vielen festen Möglichkeiten vergleichen möchtest. Statt einer langen Kette von
else if-Vergleichen schreibst du eine übersichtliche Liste von
case-Labels — der Compiler erkennt das Muster und kann sogar
intern Sprungtabellen bauen, die schneller sind als if/else.
switch (race)
{
case "elf":
write("Du bist ein Elf.\n");
break;
case "zwerg":
write("Du bist ein Zwerg.\n");
break;
default:
write("Unbekannte Rasse.\n");
}
Aufbau eines switch
switch (ausdruck) — der zu vergleichende Wert.
Kann ein int oder ein string sein.
case wert: — eine konkrete Möglichkeit.
Mehrere cases pro switch sind die Regel.
break; — beendet den case-Zweig.
Ohne break läuft der Code in den nächsten Zweig weiter (Fall-through)!
default: — wird ausgeführt, wenn keiner der
case-Werte zutrifft. Optional, aber empfohlen.
Häufige Fehlerquelle: break vergessen
Ohne break läuft der Code in den nächsten case-Block weiter
— das ist Fall-through. In LPC ist das wie in C erlaubt, aber selten gewollt:
// FALSCH: break vergessen — beide Texte werden ausgegeben!
switch (race) {
case "elf":
write("Du bist ein Elf.\n");
// break vergessen → flow-through!
case "zwerg":
write("Du bist ein Zwerg.\n"); // wird AUCH bei "elf" ausgeführt
break;
}
Es gibt seltene Fälle, wo Fall-through gewollt ist — dann markiere ihn klar:
switch (kommando) {
case "n":
case "norden":
return go_north(); // beide Aliase führen zur gleichen Aktion
case "s":
case "süden":
return go_south();
}
Zusatz-Features im Midgard MUD
LPC-switch kann mehr als sein C-Vorbild — siehe Abschnitt 6.11a für
Bereiche (case 0..49:) und String-Mehrfach-Cases.
Das macht switch oft die elegantere Wahl gegenüber langen
if/else-Ketten.
Wann switch, wann if/else?
switch wenn du einen einzigen Wert (Zahl oder String)
gegen viele konkrete Möglichkeiten vergleichst.
if/else wenn die Bedingungen unterschiedliche
Variablen oder komplexe Ausdrücke prüfen.
6.11a switch mit Bereichen und Strings
LPC-switch kann mehr als sein C-Vorbild. Du darfst Bereiche
(case 1..5:) und Strings als Labels verwenden. Das ist im
Midgard MUD extrem nützlich für Würfeltabellen oder Eingabe-Parsing.
Bereiche (Number-Ranges)
switch(random(100))
{
case 0..49:
write("Nichts passiert.\n");
break;
case 50..89:
write("Du fühlst einen leichten Schwindel.\n");
break;
case 90..98:
write("Eine warme Welle der Heilung durchströmt dich!\n");
this_player()->buffer_hp(20, 5); // val + rate, beide Pflicht
break;
case 99:
write("KRITISCHER TREFFER! Volle Heilung!\n");
this_player()->restore_hit_points(this_player()->QueryProp(P_MAX_HP));
break;
}
Strings als Labels
Klassisches Beispiel: ein
[ Bewegungs-Befehl ],
der mehrere Schreibweisen für die Himmelsrichtung akzeptieren soll:
switch(verb)
{
case "norden":
case "n":
return go_north();
case "süden":
case "s":
return go_south();
default:
notify_fail("Wohin willst du gehen?\n");
return 0;
}
Fall-Through ist erlaubt — und gefährlich
switch(level)
{
case 1:
write("Du bist sehr unerfahren.\n");
// Fall-through (KEIN break!) - läuft in case 2..5 weiter
case 2..5:
write("Du solltest vorsichtig sein.\n");
break;
case 6..10:
write("Du bist erfahren.\n");
break;
}
Faustregel: Fall-Through ist nur OK, wenn er absichtlich ist.
Markiere ihn dann mit einem Kommentar // fall through.
6.11b foreach in voller Pracht
Die foreach-Schleife ist im Midgard MUD die idiomatische Wahl.
Sie kann mehr, als die meisten Anfänger wissen.
Variante 1: Über ein Array
foreach(object ob : all_inventory(this_player()))
write(ob->name(WER) + "\n");
Variante 2: Über einen Integer (Zählschleife)
foreach(int i : 10) // 0..9
write("i = " + i + "\n");
Variante 3: Über einen Bereich
foreach(int i : 1..6) // 1..6 (inklusive)
write("Würfel: " + i + "\n");
Variante 4: Über ein Mapping
mapping stats = ([ "str":15, "dex":12, "int":18 ]);
// nur Schlüssel:
foreach(string key : stats)
write(key + "\n");
// Schlüssel + Wert (Width 1):
foreach(string key, int wert : stats)
write(key + " = " + wert + "\n");
Variante 5: Mit Referenz (verändert das Original!)
int *werte = ({ 1, 2, 3, 4 });
foreach(int v : &werte)
v *= 10;
write(sprintf("%O\n", werte)); // ({ 10, 20, 30, 40 })
Variante 6: Über String-Zeichen
foreach(int c : "Hallo")
write(c + " "); // 72 97 108 108 111
Variante 7: break und continue
foreach(object ob : all_inventory(this_player()))
{
if (!ob->QueryProp(P_WEIGHT)) continue; // überspringen
if (ob->QueryProp(P_WEIGHT) > 5000) break; // abbrechen
// ...
}
Lies das alles in /doc/LPC/foreach.
6.11c Der Conditional-Operator ?:
Der ternäre Operator ist eine kompakte Form von if/else:
int hp = this_player()->QueryProp(P_HP);
string state = (hp > 50) ? "gesund" : "verletzt";
// Statt:
string state2;
if (hp > 50) state2 = "gesund";
else state2 = "verletzt";
Im Midgard MUD oft für Pluralformen oder Geschlechter:
write("Du siehst " + (count == 1 ? "einen" : count + "")
+ " Drachen.\n");
string er_sie = (gender == MALE) ? "er" : "sie";
Faustregel: Wenn das Ganze in eine Zeile passt und gut lesbar ist, ist ?: ok.
Verschachtelte ternäre Operatoren werden schnell unleserlich — dann lieber if/else.
6.11d Short-Circuit: && und || mit Tricks
&& und || werten von links nach rechts aus
und brechen ab, sobald das Ergebnis feststeht. Das ist nicht nur effizienter —
es ist auch ein häufig genutztes Pattern im Midgard MUD.
Sichere Objekt-Tests
// Wenn pl 0 ist, wird QueryProp(P_LEVEL) nie aufgerufen — kein Crash.
if (pl && pl->QueryProp(P_LEVEL) >= 10)
write("Zugriff erlaubt.\n");
Default-Werte mit ||
string name = pl->query_real_name() || "Unbekannt";
// Wenn query_real_name() 0/leer liefert, wird "Unbekannt" genommen.
Bedingte Aktion mit &&
// Aktion nur ausführen, wenn Bedingung wahr ist:
living(ob) && ob->Defend(10, ({DT_BLUDGEON}), 0, this_object());
// Lesbarer als:
// if (living(ob)) ob->Defend(...);
// (Aber bitte nur, wenn es kurz bleibt!)
6.11e LPC-Operatoren auf Arrays, Strings, Mappings
Was viele Anfänger nicht wissen: +, -, &, |
funktionieren auch auf Sammlungen.
// Arrays
({ 1,2,3 }) + ({ 4,5 }) // → ({ 1,2,3,4,5 })
({ 1,2,3,2,1 }) - ({ 2 }) // → ({ 1,3,1 }) alle 2en raus
({ 1,2,3 }) & ({ 2,3,4 }) // → ({ 2,3 }) Schnittmenge
({ 1,2,3 }) | ({ 3,4,5 }) // → ({ 1,2,3,4,5 }) Vereinigung
// Strings
"Hallo " + "Welt" // "Hallo Welt"
"Hallo Welt" - "l" // "Hao Wet" (alle l weg)
"abc" * 3 // "abcabcabc"
// Mappings (gleiche Width):
([ "a":1 ]) + ([ "b":2 ]) // ([ "a":1, "b":2 ])
([ "a":1, "b":2 ]) - ([ "a" ]) // ([ "b":2 ])
Diese Operatoren sind sehr schnell, weil sie Driver-intern implementiert sind.
Nutze sie statt manueller Schleifen, wann immer es passt.
6.11f Range-Indexing und Slices
Auf Arrays und Strings kannst du mit [from..to] ganze Bereiche herausschneiden.
Mit <n zählst du vom Ende.
int *a = ({ 0, 1, 2, 3, 4, 5, 6, 7 });
a[2..4] // ({ 2, 3, 4 })
a[3..] // ({ 3, 4, 5, 6, 7 }) - "ab Index 3 bis Ende"
a[..2] // ({ 0, 1, 2 }) - "Anfang bis Index 2"
a[<3..] // ({ 5, 6, 7 }) - die letzten 3
a[<3..<2] // ({ 5, 6 }) - <n> heißt "n von hinten"
Slice-Assignment (sehr mächtig)
int *a = ({ 0, 1, 2, 3, 4 });
a[2..3] = ({ 99 }); // a → ({ 0, 1, 99, 4 }) (Größe änderbar!)
a[1..0] = ({ "x", "y" }); // einfügen vor Position 1
Slice-Assignment kann Arrays vergrößern und verkleinern. Das ist im Midgard
oft eleganter als loop-basiertes Umkopieren — aber bitte nicht in heißen Schleifen.
6.11g catch() und Fehler abfangen
catch() ist keine Schleife, gehört aber thematisch hierher: Es ist der
einzige Weg, Laufzeitfehler abzufangen, ohne dass dein Code stirbt.
mixed err = catch(ob->risky_call());
if (err)
{
// err ist ein Array (im modernen LDMud) mit Fehlerdetails
log_file("error", sprintf("risky_call: %O\n", err));
}
Spezialformen (alle als Symbole im 2. Argument):
catch(expr; 'nolog) — Fehler nicht ins Log
catch(expr; 'publish) — runtime_error() trotzdem rufen
catch(expr; 'reserve, n) — eigenes Tick-Reserve setzen
Faustregel: catch() nur dort, wo du weißt, dass etwas
schiefgehen kann (fremder Code, riskante Konvertierung). Nie als Universal-Wrapper —
das versteckt echte Bugs.
6.12 Typische Anfängerfehler
- Endlosschleifen
= statt ==
break im switch vergessen
- Zu verschachtelte
if-Blöcke
foreach mit Referenz, ohne sie zu wollen (&arr)
- Slice-Assignment in heißen Schleifen — verbraucht Tonnen Speicher
catch() als Universalwrapper, der echte Fehler verschluckt
6.13 Mini-Übungen
- Schreibe eine Schleife, die von 1 bis 10 zählt.
- Prüfe, ob ein Spieler ein bestimmtes Level hat.
- Gib alle Gegenstände im Inventar aus.
6.14 Zusammenfassung
if steuert Entscheidungen
- Schleifen wiederholen Code
break und continue steuern Schleifen
switch vereinfacht viele Fälle
Kontrollstrukturen geben dir Kontrolle über dein Programm.
Ohne sie wäre LPC praktisch nutzlos.
Ausblick
In Kapitel 7 lernst du Arrays und Mappings –
komplexe Datenstrukturen, die in LPC ständig benutzt werden.
Weiter zu Kapitel 7