5.1 Was ist eine Funktion?
Eine Funktion ist ein benannter Codeblock, der eine bestimmte
Aufgabe erfüllt. Du kannst dir eine Funktion wie eine Maschine in einer Werkstatt
vorstellen: Du legst eventuell Material auf das Förderband (Parameter), die Maschine
verarbeitet es (Code im Funktionskörper), und am anderen Ende kommt vielleicht
ein Werkstück heraus (Rückgabewert).
Der entscheidende Vorteil von Funktionen ist Wiederverwendbarkeit:
Du schreibst den Code einmal und kannst ihn beliebig oft aufrufen — mit verschiedenen
Eingaben. Statt 50-mal den gleichen Block zu kopieren, schreibst du eine Funktion und
rufst sie 50-mal auf. Wenn sich später die Logik ändert, musst du sie nur an einer
Stelle anpassen.
Beispiele aus dem echten Leben für „Funktionen“:
- „addiere zwei Zahlen“ — Eingabe: zwei Zahlen, Ausgabe: eine Zahl
- „prüfe, ob ein Spieler lebt“ — Eingabe: ein Objekt, Ausgabe: 0 oder 1
- „gib mir die Beschreibung dieses Items“ — Eingabe: nichts, Ausgabe: ein String
- „heile diesen Spieler um 10 LP“ — Eingabe: Spieler, Ausgabe: nichts (nur Effekt)
- „finde alle Spieler im Raum“ — Eingabe: Raum, Ausgabe: Array von Objekten
In LPC ist fast alles eine Funktion: Ausgaben (write()),
Abfragen (QueryProp()), Aktionen (move()),
Berechnungen, Steuerung, Ereignisbehandlung. Selbst create() ist
eine Funktion — der Driver ruft sie auf, wenn dein Objekt entsteht.
Im Midgard MUD unterscheidet man drei Arten von Funktionen, die du oft hörst:
- Lfuns (Local Functions) — die du in deinen LPC-Dateien selbst schreibst
- Efuns (External Functions) — vom Driver bereitgestellte Funktionen
wie
write(), find_player(), random()
- Sefuns (Simulated Efuns) — Funktionen, die wie Efuns aufgerufen
werden, aber in der Mudlib in LPC implementiert sind, z.B.
break_string()
Je besser du Funktionen verstehst, desto sauberer wird dein Code.
Funktionen sind die Bausteine — Objekte sind nur Container für Funktionen
und Variablen.
5.2 Der grundlegende Aufbau einer Funktion
Jede Funktion besteht aus vier Teilen, die in einer festen Reihenfolge stehen:
- Rückgabewert-Typ — was die Funktion herausgibt
(oder
void, falls nichts)
- Name der Funktion — wie du sie aufrufst
- Parameterliste — was sie hineinbekommt (kann leer sein)
- Funktionskörper — der eigentliche Code, in geschweiften Klammern
int add(int a, int b) // Typ + Name + Parameter
{ // Körper Anfang
return a + b; // tut etwas und gibt das Ergebnis zurück
} // Körper Ende
Das liest sich so: „Die Funktion add bekommt zwei int-Werte
(einen a und einen b) und gibt einen int zurück
— nämlich die Summe von a und b.“
Mit Modifier davor
Im Midgard-Code wirst du oft Modifier vor dem Rückgabetyp sehen, die regeln,
wer die Funktion aufrufen darf:
public int query_hp() { return hp; }
private void internal_helper() { /* ... */ }
protected void create() { ::create(); }
varargs int log(string text, string tag) { /* ... */ }
Funktionsdeklaration vs. Funktionsdefinition
Manchmal will man die Funktion nur ankündigen, ohne sie sofort zu schreiben
— etwa, wenn zwei Funktionen sich gegenseitig rufen. Dann nutzt du eine
Deklaration (mit Strichpunkt am Ende, ohne Körper):
// Deklaration (Prototyp):
int berechne_schaden(object enemy);
// ... irgendwo später ...
// Definition (mit Körper):
int berechne_schaden(object enemy) {
return 10 + enemy->QueryProp(P_LEVEL);
}
Bei der Mudlib gibt es viele Funktionen aus geerbten Klassen, von denen
du meistens nur die Definition in /std/... findest, aber im
eigenen Code direkt anrufst — auch das funktioniert dank Prototypen.
5.3 Rückgabewerte – was kommt aus einer Funktion heraus?
Der Rückgabewert ist das Ergebnis einer Funktion. Sein Typ steht ganz vorne in der
Funktionsdefinition. Mit dem Schlüsselwort return beendest du die Funktion
und übergibst dem Aufrufer das Ergebnis. Du kannst eine Funktion auch früher mit
return verlassen — z.B. wenn eine Vorbedingung nicht erfüllt ist.
int get_level()
{
return 10;
}
Die Funktion gibt immer einen int zurück.
Das Schlüsselwort return beendet die Funktion sofort.
Alle Anweisungen nach dem return werden nicht mehr ausgeführt.
Rückgabewerte benutzen
Du kannst Rückgabewerte direkt einer Variable zuweisen oder in einem Ausdruck verwenden:
int lvl;
lvl = get_level(); // Wert in Variable speichern
if (get_level() > 5) // direkt in einer Bedingung verwenden
write("Stark!\n");
int doppelt = get_level() * 2; // in einer Berechnung verwenden
write("Level " + get_level() + "\n"); // in String-Konkatenation
Mehrere returns — frühe Ausstiege
Es ist absolut normal und sogar empfohlen, eine Funktion früh zu verlassen, sobald
klar ist, dass die weitere Logik nicht mehr nötig ist. Das hält Funktionen flach
und lesbar (manche nennen es „Guard Clauses“):
int can_enter(object pl)
{
if (!pl) return 0; // kein Spieler? raus.
if (!living(pl))return 0; // kein Lebewesen? raus.
if (pl->QueryProp(P_LEVEL) < 10) return 0; // zu schwach? raus.
// wenn wir hier ankommen, sind alle Bedingungen erfüllt:
return 1;
}
Was du nicht machen solltest: einen Wert zurückgeben, der nicht zum
deklarierten Typ passt. Wenn deine Funktion int liefern soll, gib auch
wirklich einen int zurück — nicht 0, wenn du eigentlich
ein Objekt versprochen hast (auch wenn 0 für Objekte „leer“ heißt — das funktioniert
nur, weil Objekt-Variablen 0 sein dürfen).
5.4 void-Funktionen – wenn nichts zurückkommt
Manche Funktionen sollen nichts zurückgeben, sondern nur etwas tun:
Text ausgeben, einen Zustand ändern, ein anderes Objekt benachrichtigen,
einen Effekt auslösen. Bei solchen Funktionen ist es ehrlicher, dem Compiler
und allen Lesenden zu sagen: „Hier kommt nichts zurück.“ Genau das tut der
Rückgabetyp void.
void say_hello()
{
write("Hallo!\n");
}
void heal_player(object pl, int amount)
{
if (!pl) return; // Frühausstieg: nichts zu tun
pl->buffer_hp(amount, 5); // val + rate (HP/Tick) — beide Pflicht
tell_object(pl, "Du fühlst dich besser.\n");
}
Beachte: return; ohne Wert ist in void-Funktionen erlaubt
und beendet die Funktion vorzeitig. return wert; mit Wert ist hingegen
in void-Funktionen verboten — das wäre widersprüchlich.
Was du nicht darfst
Du darfst das Ergebnis einer void-Funktion nicht speichern oder
verwenden — sie hat ja keins:
// falsch!
int x = say_hello(); // FEHLER: void hat keinen Wert
// auch falsch:
if (say_hello()) // FEHLER: kein bool-Ergebnis
write("...");
// richtig: einfach aufrufen:
say_hello();
Wann sollte man void nehmen, wann int?
Faustregel: Wenn deine Funktion etwas tut und man wissen will, ob es geklappt hat,
nimm int als Rückgabewert (1 = erfolgreich, 0 = nicht). Wenn deine
Funktion etwas tut und der Erfolg „immer“ klar ist (oder egal), nimm void:
// Aktion mit Erfolgsrueckmeldung:
int try_attack(object enemy) {
if (!enemy) return 0;
if (!living(enemy)) return 0;
enemy->Defend(10, ({DT_BLUDGEON}), 0, this_object());
return 1;
}
// Reine Aktion:
void announce(string text) {
tell_room(environment(this_object()), text);
}
Im Midgard MUD-Code wirst du void sehr häufig sehen: alle Setter
(set_hp, SetProp), alle create()-Funktionen,
alle Hook-Callbacks ohne Rückgabewert, viele Aktion-Funktionen.
5.5 Parameter – Werte an Funktionen übergeben
Parameter sind Eingabewerte, die du beim Aufruf einer Funktion
mitgibst. Sie stehen in den runden Klammern hinter dem Funktionsnamen.
Innerhalb der Funktion sind die Parameter wie ganz normale lokale Variablen
— sie existieren nur während der Funktionsausführung.
Der Trick an Parametern: Sie machen Funktionen universell. Statt
eine Funktion zu schreiben, die immer „die Zahl 5 quadriert“, schreibst du
eine Funktion, die „eine beliebige Zahl quadriert“ — und übergibst beim Aufruf
die Zahl, die dich gerade interessiert.
int square(int x) // x ist der Parameter
{
return x * x; // x als lokale Variable benutzbar
}
Aufruf:
int result;
result = square(5); // result = 25
result = square(7); // result = 49
// Auch direkt mit Variable:
int n = 10;
result = square(n); // result = 100
// Oder mit Ausdruck:
result = square(n + 2); // result = 144 (12 * 12)
Parameter sind unabhängig vom Original (Pass-by-Value)
Wenn du einen int oder string als Parameter übergibst,
bekommt die Funktion eine Kopie. Veränderungen am Parameter
wirken sich nicht auf die Originalvariable aus:
void verdoppeln(int x) {
x = x * 2; // Kopie wird verändert
write("In der Funktion: " + x + "\n");
}
void demo() {
int n = 5;
verdoppeln(n);
write("Nach dem Aufruf: " + n + "\n");
}
// Ausgabe:
// In der Funktion: 10
// Nach dem Aufruf: 5
Bei Arrays, Mappings und Objekten ist das anders: Die werden als Referenz
weitergegeben. Veränderungen wirken sich also auch außerhalb der Funktion aus
(siehe Kapitel 7 für die „Referenz-Falle“ bei Arrays/Mappings).
Parameter sind lokale Variablen innerhalb der Funktion.
Du kannst sie wie jede andere lokale Variable behandeln: ändern, in Ausdrücken verwenden,
an andere Funktionen weitergeben. Sie existieren nur während der Funktionsausführung
und verschwinden danach.
5.6 Mehrere Parameter
Funktionen können beliebig viele Parameter haben. Jeder Parameter hat einen
eigenen Datentyp und Namen, und sie werden durch Komma getrennt. Die Reihenfolge
ist wichtig — beim Aufruf musst du die Argumente in genau dieser Reihenfolge
übergeben.
string full_name(string first, string last)
{
return first + " " + last;
}
string name;
name = full_name("Odin", "Allvater"); // → "Odin Allvater"
name = full_name("Allvater", "Odin"); // → "Allvater Odin" (Reihenfolge zählt!)
Verschiedene Typen mischen
Parameter dürfen unterschiedliche Typen haben — du musst nur jeden korrekt
deklarieren:
// Spielerobjekt, Schadenshöhe, Schadenstyp:
int do_damage(object target, int amount, string dam_type) {
if (!target) return 0;
target->Defend(amount, ({dam_type}), 0, this_object());
return amount;
}
// Aufruf:
int dealt = do_damage(this_player(), 25, DT_FIRE);
Wie viele Parameter sind sinnvoll?
Faustregel: Mehr als 4–5 Parameter werden unübersichtlich. Wenn du merkst, dass
deine Funktion 7 oder 10 Parameter hat, sammle zusammengehörige Werte in einem
Mapping oder Struct, oder bilde mehrere kleinere Funktionen.
// Schwer zu lesen:
make_npc("Hrafn", "Wikinger", 12, 100, 80, 5, MALE, 0, 1);
// Besser:
mapping config = ([
"name": "Hrafn",
"race": "Wikinger",
"level": 12,
"hp": 100,
"wc": 80,
"ac": 5,
"gender": MALE,
]);
make_npc(config);
5.7 Funktionen auf Objekten aufrufen
LPC ist objektorientiert — das heißt, Funktionen leben in Objekten und werden
meistens auf einem konkreten Objekt aufgerufen. Du sagst nicht „rufe die Funktion
Name“, sondern „rufe Name(WER) auf diesem
Spieler“. Dafür gibt es den Pfeil-Operator ->.
object pl;
pl = this_player();
write(pl->Name(WER) + "\n");
Der Operator -> bedeutet: „Rufe diese Funktion auf diesem Objekt auf.“
Im Hintergrund ist das ein Aufruf der Efun call_other(objekt, "funktionsname", args...)
— der Pfeil ist nur die kurze Schreibweise. Du wirst beide Varianten in fremdem Code sehen:
// Diese drei Aufrufe sind äquivalent:
pl->Name(WER);
call_other(pl, "Name", WER);
pl.Name(WER); // neuere Syntax mit Punkt
Funktionen im eigenen Objekt
Wenn du eine Funktion im eigenen Objekt aufrufst, brauchst du keinen
Pfeil. Du schreibst einfach den Funktionsnamen:
public int query_hp() { return hp; }
public void status() {
// direkter Aufruf, weil im selben Objekt:
write("Du hast " + query_hp() + " LP.\n");
}
Vermeide this_object()->funktion() aus Faulheit — das ist langsamer
(call_other ist teurer als ein direkter Aufruf) und liest sich auch nicht besser.
Sehr wichtig: Prüfe immer, ob das Objekt existiert!
Bevor du eine Funktion auf einem fremden Objekt aufrufst, solltest du sicherstellen,
dass das Objekt überhaupt da ist. Andernfalls bekommst du einen Runtime-Error:
// Unsicher:
write(pl->Name(WER)); // Crash, wenn pl == 0
// Sicher:
if (pl)
write(pl->Name(WER));
// Oder kompakter (Short-Circuit):
if (pl && pl->QueryProp(P_LEVEL) >= 10)
write("Du bist erfahren genug.\n");
Im Midgard sind viele Funktionen geschützt: Sie geben einfach 0 zurück,
wenn der Aufruf nicht erlaubt ist (z.B. weil das aufrufende Objekt zu wenig Rechte hat).
Verlass dich nicht darauf, dass ein Aufruf immer das tut, was du erwartest. Defensives
Programmieren ist Pflicht — siehe Kapitel 11.
5.8 Frühzeitiges Verlassen einer Funktion
Mit return kannst du eine Funktion jederzeit verlassen.
Das wird oft für Prüfungen genutzt.
int can_enter(object pl)
{
if (!pl)
return 0;
if (pl->QueryProp(P_LEVEL) < 10)
return 0;
return 1;
}
Diese Struktur ist sehr typisch im Midgard MUD.
5.9 Sichtbarkeit von Funktionen
Wie bei Variablen gibt es auch bei Funktionen Sichtbarkeiten.
public int query_hp();
protected void heal();
private void internal_reset();
- public – jeder darf die Funktion aufrufen
- protected – nur dieses Objekt und Erben
- private – nur dieses Objekt selbst
Anfänger-Tipp:
Mache nur das public, was wirklich von außen gebraucht wird.
5.9a varargs – Funktionen mit variabler Argumentzahl
Viele Lib-Funktionen im Midgard MUD akzeptieren optional weitere Argumente.
Das macht man mit dem Schlüsselwort varargs:
// Die letzten Parameter sind optional und werden dann mit 0 belegt.
varargs void greet(string name, string title) {
if (!title) title = "Reisende(r)";
write(title + " " + name + ", sei willkommen!\n");
}
greet("Odin"); // -> "Reisende(r) Odin, sei willkommen!"
greet("Odin", "Allvater"); // -> "Allvater Odin, sei willkommen!"
Wichtig: varargs kommt vor dem Rueckgabetyp. Korrekte Reihenfolge:
[ modifier ] [ varargs ] [ return-type ] funktion(parameter)
// Beispiele:
public varargs int query_hp(int extra);
private varargs void log(string msg, string tag);
Sammel-Variante: alle Restargumente in ein Array
Wenn der letzte Parameter selbst varargs deklariert ist, sammelt er
alle übrigen Argumente in einem Array. Das ist die LDMud-Variante von „Rest-Args“:
varargs void log_all(string tag, varargs string * msgs) {
foreach(string m : msgs)
write("[" + tag + "] " + m + "\n");
}
log_all("DEBUG", "Hallo", "Welt", "test");
5.9b Modifier-Spickzettel
Vor Funktionen können mehrere Modifier stehen. Die wichtigsten:
public – aufrufbar von überall (Default)
protected – aufrufbar nur intern und von erbenden Objekten (NICHT per ->)
private – nur in genau diesem Objekt
static – alter Begriff für „nicht per call_other()“; verwende lieber protected
nomask – Funktion kann von erbenden Objekten nicht überschrieben werden
varargs – siehe oben
deprecated – Compiler warnt bei Aufruf
Faustregel im Midgard: So restriktiv wie möglich. public ist nicht
Standard-„weil-bequem“, sondern eine bewusste API-Entscheidung.
5.9c Inline-Closures – die kurze Schwester von lambda()
Inline-Closures sind der idiomatische Weg, kleine Funktionen direkt vor Ort zu schreiben.
Die Argumente sind $1, $2, … oder du gibst Typen explizit an.
// Kurze Form: Ausdruck
object *livings = filter(all_inventory(this_player()),
(: living($1) :));
// Mit Statements
object *kleine = filter(all_inventory(this_player()),
(: return $1->QueryProp(P_WEIGHT) < 500; :));
// Mit Typen und Kontextvariablen
int schwelle = 100;
object *teure = filter(all_inventory(this_player()),
function int (object o) : int s = schwelle
{ return o->QueryProp(P_VALUE) > s; });
Inline-Closures sind viel lesbarer als lambda() und werden im Midgard MUD
klar bevorzugt. Mehr dazu unter /doc/LPC/inline-closures.
5.10 Häufige Anfängerfehler bei Funktionen
- Falscher Rückgabewert-Typ
return vergessen
- Parameter in falscher Reihenfolge
- Objekte nicht auf
0 prüfen
- Zu große Funktionen schreiben
::create() in einem überschriebenen create() vergessen
varargs falsch positioniert (immer vor dem Rueckgabetyp!)
// falsch: kein return
int broken()
{
int x = 5;
}
5.11 Gute Funktionsnamen
Funktionsnamen sind extrem wichtig für Lesbarkeit.
- ✔
query_hp()
- ✔
set_name(string n)
- ✘
doit()
- ✘
f1()
Gute Namen sparen Erklärungen.
5.12 Mini-Übungen
- Schreibe eine Funktion
int is_even(int x).
- Schreibe eine Funktion, die einen Namen zurückgibt.
- Schreibe eine Funktion, die prüft, ob ein Spieler existiert.
int is_even(int x)
{
if (x % 2 == 0)
return 1;
return 0;
}
5.13 Zusammenfassung
- Funktionen kapseln Logik
- Parameter sind Eingaben
- Rückgabewerte sind Ergebnisse
return beendet Funktionen
- Sichtbarkeit schützt vor Fehlern
Wenn du dieses Kapitel verstanden hast, kannst du aktiv Verhalten programmieren.
5.14 Für Fortgeschrittene: Closures in LPC (Funktionen als Werte)
In vielen LPC-Beispielen siehst du Funktionsnamen als Strings: Man schreibt irgendwo "cmd_suchen",
und die Mudlib ruft diese Funktion später auf. Das funktioniert – aber es hat Grenzen: Strings sind nur Text.
Ein Tippfehler wird oft erst zur Laufzeit bemerkt, und du kannst schwer „Kontext“ mitgeben (also: welche Daten
sollen beim Aufruf schon fest eingebaut sein?). Genau hier kommen Closures ins Spiel.
Eine Closure ist vereinfacht gesagt ein „Funktionszeiger“ oder „Funktionsobjekt“:
Du speicherst nicht den Namen als Text, sondern eine echte Referenz auf ausführbaren Code. Das bedeutet:
Du kannst Closures in Variablen speichern, in Arrays oder Mappings ablegen, als Argument an Funktionen übergeben
und später ausführen. Damit wird LPC deutlich flexibler – vor allem für Callbacks, Filter, Sortierungen,
Ereignisse und generische Helferfunktionen.
1) Warum Closures? Ein Anfängerbild
Stell dir vor, du willst eine Liste von Objekten durchsuchen und nur die behalten, die eine bestimmte Eigenschaft
erfüllen. Du könntest hart eine Schleife schreiben, überall wiederholen, und jedes Mal die Bedingung anpassen.
Mit Closures kannst du stattdessen sagen: „Hier ist die Bedingung als Funktion – wende sie auf jedes Objekt an.“
Das ist sauberer, kürzer und wiederverwendbar.
2) Closure-Grundformen (das Wichtigste, ohne Overkill)
Je nach Driver/Mudlib gibt es unterschiedliche Schreibweisen. Im Midgard MUD sieht man häufig diese Form:
#'funktionsname. Das bedeutet: „Nimm eine Referenz auf diese Funktion.“
Viele Beispiele nutzen Closures auch indirekt, ohne dass es groß erklärt wird – zum Beispiel bei
filter(), map() oder bei Properties, die eine Closure akzeptieren.
// Beispiel: Closure auf eine Funktion in diesem Objekt
closure c = #'is_wizard;
// Später ausführen (konkret: über funcall)
if (funcall(c, this_player()))
write("Du bist ein Magier.\n");
int is_wizard(object who)
{
return who && IS_WIZARD(who);
}
Das neue Wort hier ist funcall(): Damit rufst du eine Closure auf, als würdest du eine Funktion
aufrufen. Du übergibst die Argumente genauso wie bei normalen Funktionen.
3) Closures mit filter() und map() (super praktisch)
Zwei sehr typische Helfer sind filter() und map(). Sie arbeiten auf Arrays.
filter() behält nur Elemente, für die die Bedingung wahr ist. map() wandelt jedes
Element um (z.B. Objekt → Name).
// Angenommen, du hast ein Array von Objekten:
object *inv = all_inventory(this_player());
// 1) Filter: nur lebende Wesen (living)
object *livings = filter(inv, #'living);
// 2) Map: aus Objekten Strings machen (z.B. object_name)
string *names = map(livings, #'object_name);
// Ausgabe
printf("Lebende Objekte: %O\n", names);
Wichtig: Hier siehst du ein schönes Prinzip: Du gibst nicht „den Namen einer Funktion als String“ an,
sondern die Funktion selbst als Closure. Dadurch ist vieles robuster. Wenn es die Funktion nicht gibt,
fällt es typischerweise früher auf. Und du kannst leichter zwischen Funktionen wechseln, ohne überall Strings
auszutauschen.
4) Closures mit Kontext (warum das so mächtig ist)
Eine häufige Anfängerfrage: „Wie filtere ich nach etwas, das ich zur Laufzeit kenne – z.B. nach einer ID,
einem Grenzwert oder einem Namen?“ Genau dafür sind Closures ideal, weil du den Kontext als Parameter
mitschicken kannst – entweder direkt beim Aufruf oder indem du eine Hilfsfunktion baust, die zusätzliche
Argumente akzeptiert.
// Wir filtern Inventar nach einer bestimmten ID (z.B. "flea").
// Idee: Hilfsfunktion, die (object ob, string id) prüft.
int has_id(object ob, string id)
{
return ob && ({int})ob->id(id);
}
void demo_filter_by_id(string id)
{
object *inv = all_inventory(this_player());
// filter() ruft has_id(obj, id) auf, wenn der Driver/Mudlib extra Argumente unterstützt
// (Im Midgard-Kontext ist das oft möglich. Wenn nicht, nimm eine andere Strategie, siehe Hinweis unten.)
object *hits = filter(inv, #'has_id, id);
printf("Gefunden: %O\n", map(hits, #'object_name));
}
Hinweis für Anfänger: Nicht jede Kombination ist in jeder Umgebung gleich. Manche Treiber/Mudlibs unterstützen
Zusatzargumente bei filter/map, andere erwarten nur (Array, Closure). Wenn Zusatzargumente
nicht gehen, löst du es klassisch mit einer Schleife – oder du baust die Logik anders (z.B. erst map → Daten,
dann manuell filtern). Die Grundidee bleibt: Closures helfen dir, Logik als „Baustein“ zu behandeln.
5) Objektbindung: Closure auf Funktion eines anderen Objekts
Closures können auch auf Funktionen in einem anderen Objekt zeigen. Das ist typisch bei Callbacks:
Du gibst einer Funktion „was sie später aufrufen soll“. Dadurch muss der Empfänger nicht wissen,
welche Funktion genau ausgeführt wird – er ruft einfach die Closure auf.
// Beispiel: Du willst, dass Objekt A später eine Funktion in Objekt B aufruft.
// (Pseudo-Beispiel, weil konkrete Syntax variieren kann.)
// In B:
void notify(string msg) { tell_object(this_player(), msg); }
// In A:
void do_callback(closure cb)
{
if (cb) funcall(cb, "Callback wurde ausgelöst.\n");
}
// Aufrufidee:
object b = this_object(); // nur als Beispiel
closure cb = #'notify;
do_callback(cb);
Warum ist das gut? Weil du „do_callback“ generisch hältst: Es kann jeden Callback ausführen,
solange er dieselbe Art von Argumenten akzeptiert. So baut man flexible Systeme (Events, Hooks,
Verzögerungen, Timer, UI-Reaktionen, Auslöser wie dein Hebel im Kapitel-13-Beispiel).
6) Typische Anfängerfehler
-
Strings mit Closures verwechseln:
"is_wizard" ist Text, #'is_wizard ist Code-Referenz.
Text kann Tippfehler verstecken – Closures sind meist robuster.
-
Falsche Argumente bei funcall: Wenn die Funktion zwei Parameter erwartet, musst du auch zwei übergeben.
Sonst knallt es (oder liefert falsche Ergebnisse).
-
Zu früh optimieren: Wenn du gerade erst Schleifen lernst, ist es okay, erst die Schleife zu schreiben.
Closures sind ein Werkzeug für Eleganz und Wiederverwendung – kein Muss für jede Kleinigkeit.
7) Wo du Closures im MUD sofort wiedertriffst
Du wirst Closures häufig sehen bei: filter, map, Sortierfunktionen,
Event/Hooks-Systemen, manchmal bei Properties (wenn eine Property statt Text eine Funktion zur Berechnung
akzeptiert), und bei Tools/Debugging (einige Mechaniken im MGtool arbeiten intern stark callback-orientiert).
Wenn du Closures verstanden hast, erkennst du viele „magische“ Patterns plötzlich als ganz normal:
„Aha, da wird Code als Wert weitergereicht.“
Empfehlung: Baue Closures zuerst in kleinen, ungefährlichen Stellen ein – zum Beispiel beim Filtern und Mappen von Arrays.
Sobald das sitzt, kannst du damit größere Systeme entkoppeln (z.B. Hebel löst Callback aus, Raum entscheidet, was passiert).
Ausblick
In Kapitel 6 lernst du Kontrollstrukturen:
Schleifen (for, while),
switch, break und typische Muster im Midgard MUD.
Weiter zu Kapitel 6
Kontrolle über den Programmfluss.