In diesem Kapitel lernst du die wichtigsten Datentypen von LPC kennen.
Datentypen sind das Fundament jeder Programmiersprache – sie bestimmen,
was ein Wert ist, was man damit tun darf und was nicht.
Wir gehen langsam vor, mit vielen Beispielen und Erklärungen für absolute Anfänger.
Danach: Variablen, Gültigkeitsbereiche und Speicher.
3.1 Was sind Datentypen überhaupt?
Ein Datentyp beschreibt, welche Art von Information in einer
Variable gespeichert wird. Stell dir verschiedene Behälter im Haus vor:
einen Briefkasten für Briefe, einen Geldbeutel für Münzen, eine Vase für Blumen.
Du kommst nicht auf die Idee, Münzen in den Briefkasten zu werfen oder eine Blume
in den Geldbeutel zu stecken — jeder Behälter hat seinen Zweck. Genauso ist es
in LPC: jede Variable hat einen Typ, und der Typ legt fest, was hineindarf und
welche Operationen erlaubt sind.
Warum ist das wichtig? Weil der Driver (das Programm, das deinen LPC-Code
ausführt) bestimmte Operationen nur auf passenden Typen sinnvoll machen kann.
Du kannst "Hallo" - "Welt" nicht subtrahieren, weil das keinen Sinn ergibt.
Du kannst aber 5 + 3 rechnen oder "Hallo" + "Welt" aneinanderhängen.
Ein Name ist etwas anderes als eine Zahl. Ein Objekt ist etwas anderes als ein Text.
LPC muss das wissen, um korrekt arbeiten zu können.
Vergleiche aus dem echten Leben:
Eine Zahl → kannst du addieren, vergleichen, in einer Schleife hochzählen
Ein Text → kannst du anzeigen, durchsuchen, mit anderem Text verbinden
Ein Objekt (z.B. ein Spieler) → kannst du bewegen, dessen Properties abfragen, eine Funktion darauf aufrufen
Eine Liste (Array) → kannst du durchgehen, das n-te Element holen, vergrößern
Ein Wörterbuch (Mapping) → kannst du nach einem Schlüssel fragen, neue Einträge hinzufügen
LPC ist beim Thema Typen streng, aber nicht kleinlich. Standardmäßig
prüft der Compiler bei Zuweisungen nur grob — wenn du aber
#pragma strict_types oder #pragma rtt_checks oben in dein
Programm schreibst, wird er strenger und fängt viele Fehler schon beim Kompilieren ab.
Genau das ist im Midgard MUD die Empfehlung, und du wirst diese Pragmas in fast jedem
Code-Beispiel sehen.
Viele Fehler von Anfängern entstehen, weil Datentypen verwechselt werden:
eine Zahl wird wie ein String behandelt, ein Objekt wird angesprochen, das
in Wahrheit gerade 0 ist (zerstört wurde), ein Mapping wird
wie ein Array indiziert. Wenn du die Datentypen sicher unterscheiden kannst,
hast du eine der größten Fehlerquellen schon im Griff. Genau das ist das Ziel
dieses Kapitels.
3.2 Der Datentyp int – ganze Zahlen
int steht für Integer, also ganze Zahlen.
Das sind Zahlen ohne Komma — positiv oder negativ. Im Midgard MUD ist
int der mit Abstand am häufigsten verwendete Datentyp,
weil sehr viele Werte ganzzahlig sind: Lebenspunkte, Spielerlevel,
Schaden, Gewicht in Gramm, Counter, Flags und Bitmasken.
Ein int ist im Midgard mindestens 32 Bit groß (auf modernen
Servern oft 64 Bit). Das bedeutet: positive Werte bis weit über zwei Milliarden
passen rein. Für alle praktischen Zwecke im MUD ist „der Wertebereich“ kein
Thema — du kannst beruhigt mit int rechnen.
Beispiele für int-Werte
0
1
-5
42
100000
Typische Verwendung im Midgard MUD:
Lebenspunkte (P_HP) und maximale Lebenspunkte (P_MAX_HP)
Level (P_LEVEL) eines NPCs oder Spielers
Zähler: wie oft wurde ein Hebel gezogen, wie viele Items wurden gefunden
Gewicht in Gramm (P_WEIGHT)
Schaden, der ausgeteilt oder eingesteckt wird
Flags als 0/1: ist die Tür offen? Hat der Spieler die Quest gelöst?
Zufallswerte aus random(n)
Zeitstempel: time() liefert Sekunden seit 1970 als int
int in einer Variable: Schaden und Heilung
int hp; // Variable deklarieren (Wert: 0)
hp = 50; // Wert zuweisen
hp = hp - 10; // Schaden — hp ist jetzt 40
hp = hp + 5; // Heilung — hp ist jetzt 45
// Kürzer mit kombinierten Operatoren:
hp -= 10; // identisch zu hp = hp - 10
hp += 5; // identisch zu hp = hp + 5
hp++; // hp = hp + 1
hp--; // hp = hp - 1
Rechnen mit int
Du hast die üblichen mathematischen Operatoren:
int a = 10 + 3; // 13 Addition
int b = 10 - 3; // 7 Subtraktion
int c = 10 * 3; // 30 Multiplikation
int d = 10 / 3; // 3 Ganzzahl-Division (KEIN Komma!)
int e = 10 % 3; // 1 Modulo (Rest)
int f = 2 * 7 + 1;// 15 Punkt vor Strich
Anfängerfalle: Ganzzahl-Division
10 / 3 ist in LPC nicht 3.33..., sondern 3.
Der Rest fällt einfach weg, wenn beide Operanden int sind.
Wenn du Kommazahlen brauchst, musst du float verwenden
(siehe Abschnitt 3.8a).
Wahrheitswerte sind int
LPC hat keinen separaten bool-Typ wie andere Sprachen.
Stattdessen gilt: 0 bedeutet „falsch“, alles andere bedeutet „wahr“.
Das ist eine der wichtigsten Konventionen in LPC:
int found = 0; // "noch nicht gefunden"
if (found) write("Hurra!\n"); // wird NICHT ausgeführt
found = 1; // "gefunden"
if (found) write("Hurra!\n"); // wird ausgeführt
// Auch typische Tests:
if (hp) { /* hp ist nicht 0 */ }
if (!hp) { /* hp ist 0 */ }
if (hp > 0) { /* explizit größer als 0 */ }
Wichtig: Es gibt keine Kommazahlen mit int.
3.5 ist kein gültiger int-Wert —
wenn du das schreibst, ist es automatisch ein float.
3.3 Der Datentyp string – Texte
Ein string ist Text — also eine Folge von Zeichen, die für Menschen
lesbar ist. Alles, was in doppelten Anführungszeichen steht, ist ein String.
In einem MUD ist string nach int der zweitwichtigste
Datentyp, weil ständig Texte ausgegeben, gespeichert und manipuliert werden:
Namen, Beschreibungen, Ausgaben an Spieler, IDs, Befehle, Lognachrichten.
Im Midgard MUD sind Strings in echtem Unicode (UTF-8) gespeichert.
Das heißt: Du kannst und sollst Umlaute (ä, ö, ü),
ein ß und auch andere Sonderzeichen direkt schreiben — das ist explizite
Konvention. Du musst sie also nicht als ae, oe, ue
oder ss umschreiben.
Strings in LPC
"Hallo Welt"
"Ein langer Text mit Leerzeichen, Umlauten ä ö ü und ß"
"123" // das ist Text, keine Zahl!
"" // leerer String
"Mit Zeilenumbruch:\n" // \n ist ein Steuerzeichen
"Mit \"Anführungszeichen\" mittendrin" // \" ist ein escaptes "
Wichtige Steuerzeichen (Escape-Sequenzen)
Manche Zeichen kannst du nicht einfach schreiben, weil sie eine Sonderbedeutung haben.
Dafür gibt es Escape-Sequenzen mit dem Backslash:
\n // Zeilenumbruch (Newline) - sehr häufig!
\t // Tabulator
\\ // ein einzelner Backslash
\" // ein Anführungszeichen innerhalb eines Strings
\r // Carriage Return (selten gebraucht)
Im Midgard endet fast jede Zeile, die du an einen Spieler ausgibst,
mit \n. Wenn dein Text seltsam zusammenklebt, hast du das wahrscheinlich
vergessen.
Strings verbinden (Konkatenation)
Mit + kannst du zwei Strings aneinanderhängen. Du kannst auch
Zahlen mit Strings verbinden — der Driver wandelt die Zahl automatisch in Text um:
Ein Trick im Midgard: Wenn du mehrere String-Literale direkt nebeneinander
schreibst, fügt der Compiler sie automatisch zusammen. Das ist eleganter als
viele Pluszeichen:
SetProp(P_LONG,
"Du stehst auf einer windgeschützten Lichtung. "
"Hohe Fichten rahmen den Ort ein, und der Boden "
"ist weich von Moos und Nadeln.\n");
// Wird zu einem einzigen langen String zusammengefügt.
Strings werden sehr oft benutzt
Namen (P_NAME) und Beschreibungen (P_SHORT, P_LONG)
Ausgaben an Spieler über write(), tell_object()
IDs, mit denen man Objekte ansprechen kann ("schwert", "kleiner stein")
Befehle wie "norden", "iss", "untersuche"
Schlüssel in Mappings: map["str"]
Pfade: "/players/odin/work/test.c"
String-Variable in der Praxis
string name;
name = "Ein alter Baum";
write(name + "\n");
// Strings haben eine Länge:
int laenge = sizeof(name); // 14
// Du kannst auf einzelne Zeichen zugreifen:
int erstes_zeichen = name[0]; // 'E' (= 69 in ASCII)
// Und Teilstrings ausschneiden:
string anfang = name[0..2]; // "Ein"
string ende = name[<4..]; // "Baum" (4 Zeichen vom Ende)
Häufiger Anfängerfehler:"10" ist nicht dasselbe wie 10.
Das erste ist Text (drei Zeichen, falls du es so siehst — eigentlich zwei: '1' und '0'),
das zweite eine Zahl. Du kannst nicht einfach mit ihnen rechnen, ohne vorher zu konvertieren.
Mit to_int("10") wird aus dem String die Zahl, mit
to_string(10) aus der Zahl der String — oder noch einfacher:
"" + 10 wandelt die Zahl in Text um, weil String-Konkatenation
automatisch konvertiert.
3.4 Der Datentyp object – Dinge in der Welt
Einer der wichtigsten Datentypen in LPC ist object. Ein „Objekt“ ist
in einem MUD alles, was eine Datei mit Code als Bauplan hat und im Speicher
des Drivers existiert. Dazu gehören:
Spieler — interaktive Objekte mit einer Verbindung
Räume — die Schauplätze des Spiels
NPCs — Monster, Händler, Wachen, Questgeber
Items — Schwerter, Rüstungen, Tränke, Bücher
Container — Beutel, Truhen, Schränke
Daemons — Hintergrundobjekte ohne Inventar (z.B. der Wetterdaemon)
Eine object-Variable speichert eine Referenz auf so ein Objekt,
nicht das Objekt selbst. Du kannst dir das wie einen Zettel mit der Adresse vorstellen:
Die Variable enthält den „Verweis", über den der Driver weiß, wo das Objekt im Speicher liegt.
Wenn das Objekt zerstört wird (destruct()), zeigt deine Variable automatisch
auf 0.
Object-Variable: Raum des Spielers holen
object room;
room = environment(this_player());
// room zeigt jetzt auf den Raum, in dem this_player() steht.
if (room) // immer prüfen!
write("Du bist in: " + object_name(room) + "\n");
Wie kommt man an Objekte?
Es gibt mehrere Standardwege, an Objekte zu kommen — sie zu erhalten gehört zum
täglichen Brot beim Coden:
object pl = this_player(); // wer hat das Kommando ausgelöst?
object self = this_object(); // ich selbst (das aktuelle Objekt)
object env = environment(self); // mein Container/Raum
object prev = previous_object(); // wer hat MICH gerufen?
object t = present("schwert", pl); // hat pl ein "schwert" dabei?
object o = find_object("/std/room"); // schon geladenes Objekt finden
object n = find_player("odin"); // Spieler "odin" online?
object l = find_living("hrafn"); // ein Lebewesen "hrafn"?
object new = clone_object("/std/thing"); // einen frischen Clone erzeugen
Funktionen auf Objekten aufrufen
Das ist die Kernidee der Objektorientierung: Ein Objekt hat eingebaute Funktionen
(„Methoden“), die du von außen aufrufen kannst. Dazu gibt es den Pfeil-Operator
->:
Funktion auf Objekt aufrufen
object pl;
pl = this_player();
write(pl->Name(WER) + "\n"); // Standard-Name im Nominativ
write(pl->QueryProp(P_RACE) + "\n"); // Rasse als String
int hp = pl->QueryProp(P_HP); // Lebenspunkte
Wenn du pl->QueryProp(P_HP) schreibst, sagst du dem Driver:
„Geh zum Objekt pl, finde dort die Funktion QueryProp,
rufe sie mit dem Argument P_HP auf und gib mir das Ergebnis zurück.“
Das wirkt unscheinbar, ist aber das Herz des MUD-Codes — fast jede Spielmechanik
besteht aus solchen Aufrufen.
Wichtigste Faustregel: Immer auf 0 prüfen!
Eine object-Variable kann jederzeit den Wert 0 haben:
Sie wurde nie zugewiesen (nur deklariert)
find_player() hat niemand gefunden
present() hat das Objekt nicht im Container gefunden
Das Objekt wurde zwischendurch destruct()-et
object enemy = find_living("ork");
if (!enemy) {
write("Kein Ork in der Nähe.\n");
return;
}
// Ab hier ist enemy garantiert kein 0:
enemy->Defend(20, ({DT_BLUDGEON}), 0, this_object());
Wer eine Funktion auf einem 0-Objekt aufruft, bekommt im Driver eine Fehlermeldung
wie „call to a not loaded object“ oder einen Runtime-Error.
Das ist eine der häufigsten Bugquellen für Anfänger.
Merksatz: Ohne Objekte gibt es kein MUD. LPC ist eine
objektorientierte Sprache, und Objekte sind buchstäblich überall —
sogar du selbst, während du den Code schreibst, bist im Spiel ein Objekt.
3.5 Der Datentyp mixed – alles und nichts
mixed ist ein besonderer Typ — eine Art „Joker“. Er kann
jeden anderen Datentyp enthalten: heute eine Zahl, morgen
einen String, übermorgen ein Objekt. Der Compiler prüft bei einer
mixed-Variable nicht, was du hineinsteckst.
Auf den ersten Blick klingt das praktisch: keine Typprobleme, du kannst
einfach loslegen. Aber damit verlierst du auch den Schutz, den der Compiler
normalerweise bietet. Wenn die Variable mal ein int, mal
ein string ist, weiß spätere Logik nicht mehr, womit sie es
gerade zu tun hat. Genau deshalb gilt im Midgard MUD die klare Konvention:
mixed wird vermieden, wenn ein konkreter Typ möglich ist.
mixed in Aktion (zur Demonstration)
mixed x;
x = 10; // x ist jetzt ein int
x = "Hallo"; // x ist jetzt ein string
x = this_player(); // x ist jetzt ein object
x = ({ 1, 2 }); // x ist jetzt ein int*
x = ([ "a":1 ]); // x ist jetzt ein mapping
Wann ist mixed doch sinnvoll?
Es gibt Situationen, in denen du mixed wirklich brauchst:
Eine Property, die wahlweise ein String, eine Closure oder ein Array sein kann
(z.B. P_LONG akzeptiert string oder closure).
Ein Mapping, in dem die Werte unterschiedliche Typen haben:
mixed *daten
Eine Funktion, die unterschiedliche Ergebnistypen liefern kann
(selten — meistens besser auflösbar mit Union-Typen wie int|string).
Beim Lesen aus QueryProp() bekommst du oft mixed zurück,
weil Properties beliebig sein können.
Bessere Alternative: Union-Typen
Wenn du genau weißt, dass eine Variable nur einer von zwei oder drei Typen sein kann,
nutze einen Union-Typ statt mixed. Das ist viel sauberer:
int|string foo; // foo ist int oder string, nichts anderes
object|closure cb; // Callback: ein Objekt ODER eine Closure
Vor- und Nachteile
✔ Flexibel — passt sich jedem Wert an
✘ Compiler hilft nicht mehr — Tippfehler werden erst zur Laufzeit erkannt
✘ Schwerer zu lesen — wer das Gesehene nicht kennt, weiß nicht, womit er rechnen muss
✘ Mehr Bugs möglich — falsche Operationen schlagen erst zur Laufzeit fehl
✘ Schlechtere Performance — strict_types kann nicht optimieren
Empfehlung: Verwende mixed nur, wenn du es wirklich brauchst.
In 90 % der Fälle weißt du, was eine Variable enthält — dann benutze auch den passenden
Typ. Wenn du in fremdem Code mixed siehst, lies sehr aufmerksam, was wirklich
drinsteht.
3.6 Arrays – mehrere Werte in einer Variable
Ein Array ist eine geordnete Liste von Werten. Statt vieler einzelner
Variablen, die nummeriert sind (name1, name2, …),
steckst du sie alle in eine Variable, die eine Reihenfolge merkt.
Im MUD wirst du Arrays ständig brauchen: das Inventar eines Spielers ist ein
Array von Objekten, eine Liste von IDs für ein Item, eine Liste von Räumen
einer Patrouille.
In LPC schreibt man Arrays mit den Klammern ({ ... }).
Das ist die wichtigste Notation, die du dir merken musst — diese
Klammerform unterscheidet ein Array vom „nichts“. Ein einfacher Stern *
nach dem Typ markiert den Variablentyp als „Array von …“.
Array mit Zahlen
int *numbers; // Variable: "Array von int"
numbers = ({ 1, 2, 3, 4 });
// Vier Zahlen, geordnet, mit Indizes 0, 1, 2, 3
Anders als im täglichen Leben („Das erste Brötchen, das zweite Brötchen, …“)
zählt LPC ab 0. Das ist in fast allen Programmiersprachen so —
gewöhne dich früh daran:
Mit sizeof() erhältst du die Anzahl der Elemente. Das brauchst du
bei Schleifen oder Existenz-Tests:
if (sizeof(names) > 0) // Array nicht leer?
write("Erstes: " + names[0] + "\n");
for (int i = 0; i < sizeof(names); i++)
write(names[i] + "\n");
Arrays sind Referenzen — Vorsicht!
Wenn du eine Array-Variable einer anderen zuweist, hast du nicht
zwei separate Arrays — beide Variablen zeigen auf dasselbe Array.
Veränderungen an einem sind dann auch beim anderen sichtbar:
int *a = ({ 1, 2, 3 });
int *b = a; // KEINE Kopie! Beide zeigen auf dieselbe Liste.
b[0] = 99;
write(a[0] + "\n"); // 99 ! a wurde mitverändert.
// Wenn du eine echte Kopie willst:
int *c = a + ({}); // a + leeres Array = neue Kopie
Diese „Referenz-Falle“ ist eine der häufigsten Bugquellen in LPC. Merken!
(Mappings haben übrigens dasselbe Verhalten — siehe Kapitel 7.)
Array vs. Liste: Indizes überschreiten ist ein Fehler
Wenn du ein Element jenseits der Größe ansprichst, gibt es einen Runtime-Error.
Prüfe also vorher, ob der Index gültig ist, oder verwende sicherere Konstrukte
wie foreach:
int *a = ({ 1, 2, 3 });
write(a[10]); // FEHLER: Index out of range
// Sicher:
if (10 < sizeof(a))
write(a[10]);
// Noch besser: foreach
foreach(int x : a)
write(x + "\n");
Mehr Operationen auf Arrays (Vereinigung, Subtraktion, Slicing) erklärt
Kapitel 7 ausführlich.
3.7 Mappings – Schlüssel-Wert-Paare
Ein Mapping ist die zweite große Sammeldatenstruktur neben Arrays. Es ist
vergleichbar mit einem Wörterbuch oder einem Karteikartenkasten: zu jedem
Schlüssel gibt es einen Wert. Statt „Element Nr. 3“
wie beim Array fragst du beim Mapping nach einem benannten Eintrag —
zum Beispiel: „Was steht unter dem Schlüssel str?“
Mappings sind im MUD ideal für alles, wo Zuordnungen wichtig sind und die
Reihenfolge keine Rolle spielt: Spielerattribute, Konfigurationen,
Suchindizes, Quest-Status, Detail-Beschreibungen eines Raums.
Die Notation: Mappings stehen in Klammern ([ ... ]) (mit eckigen
Klammern, anders als Arrays, die runde Klammern verwenden:
({ ... })). Schlüssel und Wert werden mit einem Doppelpunkt
getrennt; einzelne Einträge mit Komma.
Werte lesen
Wie bei Arrays nutzt du eckige Klammern [] — aber statt einer Zahl
gibst du den Schlüssel an:
Wert aus Mapping lesen
write(stats["str"] + "\n"); // 10
write(stats["int"] + "\n"); // 15
// Wenn der Schluessel nicht existiert, gibt's KEINEN Fehler:
write(stats["foo"] + "\n"); // 0 (Default-Wert)
Das ist anders als bei Arrays: Wenn du einen nicht existierenden Schlüssel
abfragst, bekommst du einfach 0 (oder den entsprechenden Default
des Werttyps). Wenn du also wissen willst, ob ein Schlüssel wirklich
existiert, nutze member(map, key).
Werte schreiben
Auch das geht über die eckige Klammer — neue Schlüssel werden automatisch
angelegt:
Spielerattribute (siehe
[ Hilfe: Attribute ]):
P_ATTRIBUTES ist ein Mapping mit
Schlüsseln "str", "int", "dex", "con"
Properties intern: das gesamte Property-System eines Objekts
basiert auf Mappings
Konfigurationen: ein Raum definiert seine Details als Mapping
(Schlüssel = ID, Wert = Beschreibung)
Quest-Status: Mapping mit Quest-Namen → 0/1 (gelöst?)
Caches: beim Suchen einmal gefunden, dann unter dem Suchbegriff abgelegt
Sets (Mengen): Mappings mit Width 0 (siehe Kapitel 7)
Mappings sind — wie Arrays — Referenzen. Wenn du eine Mapping-Variable
einer anderen zuweist, hast du nicht zwei separate Mappings.
Mehr dazu in Kapitel 7 inkl. copy() und Width.
3.8 void – kein Rückgabewert
void ist kein Datentyp im engeren Sinne — du kannst keine
void-Variable anlegen. Stattdessen nutzt du void
als Rückgabewert einer Funktion und sagst damit dem Compiler
(und allen, die deinen Code lesen): „Diese Funktion gibt nichts zurück.“
Wann ergibt das Sinn? Immer dann, wenn eine Funktion etwas tun soll,
aber kein Ergebnis liefert: Text ausgeben, einen Zustand ändern, ein anderes
Objekt benachrichtigen. So eine Funktion ist quasi eine „Aktion“.
void-Funktion: einfach Text ausgeben
void say_hello() {
write("Hallo!\n");
}
// Aufruf — kein Wert zum Speichern:
say_hello();
// Das wäre ein Fehler:
int x = say_hello(); // FEHLER: void hat keinen Wert
Vergleich
void inform_room(string text) { tell_room(environment(), text); }
// Rückgabe: nichts
int query_hp() { return hp; }
// Rückgabe: ein int
string Name() { return p_name; }
// Rückgabe: ein string
object find_owner() { return owner; }
// Rückgabe: ein object
Faustregel: Wenn deine Funktion irgendwann ein return wert;
haben soll, muss der Rückgabetyp passen — nicht void. Wenn deine Funktion
nur etwas tun soll, ist void die richtige Wahl, und ein einfaches
return; (ohne Wert) reicht, um die Funktion vorzeitig zu beenden.
Im Midgard MUD-Code wirst du void sehr häufig sehen: alle Setter,
alle create()-Funktionen, alle Hook-Callbacks, viele Aktion-Funktionen.
Das ist die Norm.
3.8a Weitere LPC-Datentypen, die du kennen solltest
Neben int, string, object, mixed,
Arrays und Mappings gibt es in LDMud (dem Driver, auf dem das Midgard MUD läuft)
noch ein paar weitere Datentypen. Du brauchst sie nicht sofort – aber du solltest
sie wiedererkennen, wenn du in fremdem Code darauf stoesst.
float – Gleitkommazahlen
Zahlen mit Komma. Im MUD selten, aber z.B. für Resistenzen
(P_RESISTANCE_STRENGTHS) oder bei Wahrscheinlichkeiten in NPCs:
Seit LDMud 3.6 wird zwischen string (Unicode-Text) und bytes
(rohe Byte-Sequenz) unterschieden. Strings können Umlaute und alle Unicode-Zeichen,
bytes sind binaere Daten. Die zwei Typen darfst du nicht mischen.
string s = "Hallo, Welt mit Umlauten: ä, ö, ü, ß";
bytes b = b"binary data";
closure – Code als Wert
Eine Referenz auf eine Funktion. Du kannst sie speichern, weitergeben und später
ausführen. Im Midgard sehr verbreitet: Set-/Querymethoden auf Properties, Filter,
Sortierfunktionen, Hooks. Mehr dazu in Kapitel 5.
Symbole sehen aus wie Strings, sind aber „normalisiert“ und werden in Closures für
Variablen-Namen genutzt: 'x steht für das Symbol mit dem Namen
x. Du brauchst sie fast nur in Lambdas.
struct – benannte Datensaetze
Eine Sammlung mehrerer Werte mit benannten Feldern (vergleichbar mit C-Strukturen
oder Datenklassen). Werden in der Midgard-Mudlib seltener verwendet, sind aber möglich:
struct Coordinate { int x; int y; };
struct Coordinate c = (<Coordinate> 5, 7);
write(c->x + ":" + c->y + "\n"); // 5:7
lwobject – Lightweight Objects
Eine leichtgewichtige Variante von Objekten ohne Inventar/Environment.
Fuer Anfänger nicht relevant – aber gut zu wissen, falls du
lwobject in modernem Code siehst.
Typ-Tests im Code
Bevor du auf einem Wert eine Operation ausführst, kannst du seinen Typ pruefen:
if (intp(x)) write("Ganzzahl\n");
if (stringp(x)) write("Text\n");
if (objectp(x)) write("Objekt\n");
if (pointerp(x)) write("Array\n");
if (mappingp(x)) write("Mapping\n");
if (closurep(x)) write("Closure\n");
if (floatp(x)) write("Float\n");
Diese Tests sind in defensivem Code Pflicht (siehe Kapitel 11/12).
3.8b Strings: Indizes, Slicing und Umlaute
LPC-Strings können wie Arrays indiziert werden – jedes Zeichen ist intern eine
ganze Zahl (Unicode-Codepoint).
string s = "Test";
int c = s[0]; // 'T' (= 84)
string sub = s[1..2]; // "es"
string end = s[<2..]; // "st" (von 2. von hinten bis zum Ende)
int n = sizeof(s); // 4
Mit <n zählst du vom Ende, mit .. machst du Slices.
Strings sind veränderlich: s[0] = 't'; ändert tatsaechlich den String.
Im Midgard MUD werden Texte in echtem UTF-8 gespeichert. Schreib also
"hübsch", "dass", "Würfel" – nicht
"huebsch", "dass", "Wuerfel". Das ist Konvention
und wird im Code-Review erwartet.
3.9 Typische Anfängerfehler
Zahl und String verwechseln
Objektfunktionen auf Strings anwenden
mixed überall benutzen
Array-Indizes falsch zählen
string und bytes mischen (geht nicht)
Umlaute kuenstlich ersetzen, statt direkt UTF-8 zu nutzen
Diese Fehler sind normal. Wichtig ist, sie zu erkennen und zu verstehen.
3.10 Zusammenfassung
int → ganze Zahlen
string → Text
object → Dinge in der Welt
mixed → alles (mit Vorsicht)
Arrays → Listen
Mappings → Schlüssel-Wert-Paare
Wenn du diese Datentypen sicher beherrschst, wird LPC deutlich einfacher.
Ausblick
Im nächsten Kapitel lernst du, wie Variablen definiert werden,
wie lange sie existieren und wo sie sichtbar sind.