Birgit-Nietsch.de/Muds/Unitopia/LPC-Kurs

LPC

Fenchurch's LPC-Kurs - noch im Aufbau!

  1. Lektion: Objekte, Kommentare, einfache Variablen
  2. Lektion: Funktionen, globale und lokale Variablen
  3. Lektion: Operatoren und Befehle

2. Lektion: Funktionen, globale und lokale Variablen

Funktionen

Funktionen sind das, was ein Objekt ueberhaupt erst zu einem Objekt macht. Denn ohne Funktionen erfahren wir nichts ueber den Zustand eines Objekts, und damit waere das Objekt ziemlich sinnlos. Ausserdem sind Funktionen das einzige, was irgendwas tut.

Was koennen denn Funktionen tun?

Wie benutze ich eine Funktion?

Zunaechst mal, sei dir klar um was fuer eine Funktion es geht, denn sonst kannst du sie nicht richtig benutzen. Du solltest wissen, was fuer einen Zweck eine Funktion hat, was fuer Werte sie benoetigt um zu arbeiten, was fuer Auswirkungen sie hat, und was fuer Werte sie zurueckgibt.

Es gibt mehrere Wege, sich ueber Funktionen zu informieren. Zum Beispiel indem man den Code liest, in dem die Funktion in der Mudlib programmiert wurde. Vorausgesetzt, man weiss wo sie drin steckt. In Unitopia hast du es besonders leicht, denn es gibt ja zum Glueck die Enzyklopaedia Unitopia.

dek Namens-Teil gibt die die Deklarationen aller Funktionsnamen aus, die den Namens-Teil im Namen enthalten, das heisst: Rueckgabe-Typ Funktionsname (Argumente mit ihren Typen)
? Funktionsname gibt dir den/die Hilfetext/e zu einer Funktion. Den/die? Ja, manchmal tun gleichnamige Funktionen in verschiedenen Objektsorten verschiedene Dinge. Unten am Ende der Hilfeseite steht, wo die Funktion her ist. Einige Hilfeseiten sind auch auf englisch geschrieben.

Vorsicht! Bei manchen Funktionen steht auf der Hilfeseite sowas wie:

Also, sowas sollte man dann wirklich nicht mehr nehmen. Normalerweise steht aber auf derselben Hilfeseite, was man dann statt dessen benutzen soll.

Die einfachste Art, eine Funktion zu benutzen, ist, den Funktionsnamen in eine Zeile zu schreiben, ein paar runde Klammern dahinter zu setzen zwischen die man eventuell mitzugebende Werte klemmen kann, und dahinter ein Semikolon zu setzen:

   set_name("apfel");
   set_amount(40);
   set_hidden_until_next_move();

Eine weitere Moeglichkeit besteht darin, den Funtionsaufruf da unterzubringen, wo man sein Ergebnis (den Rueckgabewert) benoetigt, zum Beispiel so:

   set_long( Der() + " sieht genau so aus wie du es erwarten wuerdest.");
    halbes_gewicht = query_weight() / 2;

Bei Funktionen die keinen Rueckgabewert haben laesst man das allerdings besser bleiben. Erstens geben die eine Null zurueck, und wenn ich in einer Operation eine Null brauche kann ich das klarerweise auch gleich selber hinschreiben. Und zweitens wird der Driver, wenn er sieht dass eine Funktion keinen Rueckgabewert hat, meckern wenn er damit weiter rechnen soll.

Auf jeden Fall aber endet eine Anweisung immer mit einem Semikolon, und zu jeder sich oeffnenden Klammer gehoert eine schliessende:

anzahl_fruechte = anzahl_aepfel + anzahl_birnen;
    geschlecht = query_gender();
    set_adjektiv("gruen");

Es gibt in Unitopia folgende Arten von Funktionen:

Ein Beispielobjekt: apfel.c

Achtung: Meine Beispiele funktionieren nur mit der UNItopia Mudlib!

// Datei: apfel.c
// Die solltest du in ein Verzeichnis namens obj legen

inherit "/i/object/nahrung"; // erbe die Funktionen der Standard-Nahrung

void create()                // wenn dies Objekt erzeugt wird...
{
    ::create();              // tu erstmal, was die Standard-Nahrung taete
    set_name("apfel");       // Hauptname, den man sieht wenn's daliegt
    set_gender("maennlich"); // DER Apfel.

    add_id("apfel");         // damit "nimm apfel" weiss wer gemeint ist
    add_id("frucht");        // oder "nimm frucht"
    add_id("obst");          // oder nimm obst

    set_amount(40);          // Kalorien? Joule? Futterpunkte! :-)

    // Jetzt wollen wir noch wissen wie der Apfel aussieht
    set_long("Ein leckerer, rotbackiger Apfel. Er ist rund und prall "
      + "und sieht sehr saftig aus. An seinem Stiel haengt noch "
      + "ein frisches, gruenes Blatt.");
}

Was genau passiert in apfel.c?

All die Funktionen, die ich im Apfel benutze, mussen irgendwo programmiert sein. Sonst wuesste der Driver ja nicht was ich von ihm will. Damit also bei dem was ich treibe ein Apfel herauskommt, den ich nehmen, ansehen und essen kann, erbe ich erstmal die Eigenschaften der Standard-Nahrung, und das tu ich mit der Zeile:

inherit "/i/object/nahrung";

Dieses inherit ist keine Funktion sondern ein Befehlswort. Der Inherit-Befehl beginnt mit dem Wort inherit, dann kommt eine Zeichenkette (also ein Text in Gaensefuesschen) in der der Pfad eines besonderen Objekts steht, und am Ende steht ein Semikolon.

Der Befehl inherit kommt immer ganz vorne am Anfang eines Objekts, noch bevor irgendwelche Funktionen oder Variablen oder #include oder #define oder sonstige Sachen daherkommen. Das einzige was vor inherit steht, sollten bestenfalls Kommentare sein. Ausserdem benutzt man moeglichst wenige inherits, naemlich maximal die die man braucht. Fuer deine ersten Uebungen in LPC wirst du kaum in die Lage kommen, mehr als ein inherit pro Objekt zu verwenden. Aber dazu spaeter mehr. Viiiiiel spaeter. ;-)

Als naechstes definiere ich die Funktion create(), das heisst, ich schreibe die Funktion. Das beginnt mit dem Funktionskopf:

void create()
{

Ob dabei die sich oeffnende geschweifte Klammer in derselben Zeile steht wie das create(), oder eine Zeile tiefer, ist egal. Wichtig ist allerdings, dass ich zu der sich oeffnenden geschweiften Klammer auch irgendwo unten eine schliessende Klammer schreibe:

}

Sonst schmollt naemlich der Driver, weil er dann nicht weiss wo der Block zuende ist. Block? Ja, so nennt man alles, was zwischen einem Paar geschweifter Klammern eingeschlossen ist. Eine Funktion besteht aus einem Funktionskopf und einem Block. (Dass in einem Block wiederum Bloecke stehen koennen wird erst spaeter interessant.)

Nun kommt folgende seltsame Anweisung:

::create();

Was das? Zunaechst mal ist es eine abgekuerzte Schreibweise fuer:

nahrung::create();

Wir haben die Standard-Nahrung mit dem inherit geerbt, und sie ist unser einziges inherit, darum konnte ich die Bezeichnung Nahrung weg lassen. Auch in dem inherit gibt es eine Funktion create(), die bestimmte Einstellungen macht. Zum Beispiel die ID "nahrung" setzen. Und diese Ur-Funktion rufe ich in meinem create() als erstes auf, danach mache ich meinen eigenen Kram.

Was, create() gibt es schon? Und ich kann die nochmal schreiben? - Ja. Sowas nennt man "eine Funktion ueberladen" oder "redefinieren" oder auch "ueberlagern". Wie auch immer, es gibt schon eine, aber jetzt schreib ich meine eigene, und wer jetzt create() in meinem Objekt aufruft, kriegt eben mein create() und nicht das Ur-create(). Das geht im Prinzip mit allen Funktionen (zu Ausnahmen kommen wir spaeter), vorausgesetzt, meine Funktion verhaelt sich nach aussen so wie das ueberlagerte Original. D.h. gleicher Rueckgabetyp und gleiche Art der mitgegebenen Werte. An die Original-Funktion komme ich, wie gesagt, ran indem ich ::create() aufrufe.

Den Rest, der in meinem create() abgeht, muesstest du auch so verstehen koennen: einfache Funktionsaufrufe, jeder abgeschlossen mit einem Semikolon.

Funktionsdeklarationen

Dass man Variablen anmelden (deklarieren) muss, habe ich dir in der vorigen Lektion schon erklaert. Jetzt erzaehle ich dir dass man das mit Funktionen eigentlich auch tun muss, zumindest dann, wenn man sie benutzt bevor man an der Stelle angekommen ist an der die Funktion programmiert worden ist.

Eine Deklaration einer Funktion sieht so aus:

Rueckgabetyp Funktionsname( Variablentyp Variablenname, ...);

Beispiele fuer Funktionsdeklarationen:

void reset();
string query_name();
int query_amount();
int living(object what);

Dabei heisst void, dass die Funktion nix zurueck gibt.

Beispiele zum Vergleich:

int query_amount();        // Deklaration der Funktion query_amount()
int amount;                // Deklaration der Variablen amount, eine Zahl

int query_amount()         // programmierte Funktion query_amount()
{ 
    return amount; 
} 
string query_naehrstoffgehalt()
{
    return Der() + " hat "
    + query_amount()        // Aufruf von query_amount()
    + " Futterpunkte.";
}

Funktionen werden zu Beginn eines Objekts deklariert, bevor irgendwelche Funktionen programmiert werden. Der Uebersicht halber macht man das gerne so: Zuerst kommt inherit, dann kommen #include und #define Zeilen, dann deklariert man seine selbstgeschriebenen Funktionen, und dann deklariert man die globalen Variablen. (Wozu #include und #define da sind kommt spaeter, erstmal soll dir reichen dass es soetwas ueberhaupt gibt.)

Globale und lokale Variablen

Dass Variablen angemeldet, deklariert, werden muessen, hab ich schon gesagt und beschrieben. Nun kommt ein Weiteres hinzu: der Bereich, in dem eine Variable bekannt ist.

Eine Variable, die ausserhalb aller Funktionen angemeldet wurde, ist global. Das bedeutet: Das Objekt hebt den Wert der Variablen die ganze Zeit ueber auf, und jede Funktion dieses Objekts kann diese Variable benutzen, ihren Wert abfragen oder ihn veraendern.

Anders ist es mit Variablen innerhalb einer Funktion: Erstens kennt nur die Funktion diese Variablen, in der sie auch angemeldet wurden. Und zweitens wird ihr Wert nicht aufbewahrt, sondern jedes Mal auf null gesetzt wenn die Funktion neu aufgerufen wurde. So koennen zum Beispiel ein Dutzend Funktionen jede ihre Zaehlvariable i haben, und doch kommen sie nicht durcheinander, wenn i jeweils lokal definiert wurde, also wenn es direkt in der Funktion angemeldet wurde die das i benutzen soll.

Beispiel: baumkrone.c

// Datei baumkrone.c

inherit "/i/room";

void set_max_aepfel(int zahl);
int  query_max_aepfel();
void reset();

int max_aepfel;

void create()
{
    ::create();
    set_short("In der Krone eines Apfelbaums");
    set_long("Du sitzt in den Aesten eines krummen alten "
           + "Apfelbaums. Um dich herum sind viele Zweige "
           + "voller glatter, dunkelgruener Blaetter.");
    add_exit("workroom", "runter")
    set_max_aepfel(random(5)+1);
    reset();
}

void reset()
{
    int i;
    object dieser_apfel;

    if(!present("apfel"))
    {
         for(i=query_max_aepfel(); i>0; i--)
         {
             dieser_apfel = clone_object(abs_path("./obj/apfel"));
             dieser_apfel/move(this_object());
         }
    }
}

void set_max_aepfel(int zahl)
{
    max_aepfel = zahl;
}

int  query_max_aepfel()
{
    return max_aepfel;
}

Schau dir das Beispiel an, und ueberlege, was was ist:

for(x;y;z), clone_object() und abs_path() musst du noch nicht verstehen, das machen wir spaeter, sonst wirds hier zuviel auf einmal. :-)

Wenn du den Raum betreten koennen willst, muss die Datei in das selbe Verzeichnis wie dein Workroom.

Sodele, ist etwas lang geworden, und ich hab noch keine Zusammenfassungen unter den Abschnitten und jetzt am Ende... ich hoffe, du hast dennoch nicht die Uebersicht verloren. Die naechste Lektion werd ich versuchen etwas uebersichtlicher hinzubekommen, denn da soll es um grundlegende LPC-Befehle gehen. Also for und while und if... und um Operatoren.