Tutorial
Eine Einführung in die Welt der CommandPuppets


Um mit CommandPuppets arbeiten zu können braucht man einen Editor, der "ASCII-Texte" abspeichert (zB der normale Editor bzw. Notepad unter Windows und etwas Web-Space (=eine eigene Web-Seite). Woher man sowas nimmt, dazu frage man irgend einen Computer-Experten, von denen man bestimmt einen kennt, oder schaue im Puppet-Forum.

Ein HALLO-Puppet

Um mit einem einfachen Test-Puppet herauszufinden, wie man Puppets startet, hier ein kleines Test-Puppet.

PUPPET Hallo-Puppet

ACTION start
  >> Huuuuuhuuuuuuu, es hat funktioniert! *freu* *hüpf*
END

PUPPETEND
Tipp dieses Puppet einfach mal mit deinem Editor ein, speichere es unter dem Namen "test.prog" und "uploade" es dann in deinen Web-Space. Angenommen, dein Puppet ist unter der URL

http://www.brettspielwelt.de/Other/Puppets/test.prog

zu finden (d.h. wenn du diese URL in einen Web-Browser eingibst, dann zeigt der dein Puppet), dann kannst du es jetzt starten, indem du folgende Zeile eingibst:

/puppet CommandPuppet MeinErstesPuppet http://www.brettspielwelt.de/Other/Puppets/test.prog

Wenn du alles richtig gemacht hast, bekommst du folgende Ausgabe (ohne die Zeilennummern):

(1) -- Lade CommandPuppet MeinErstesPuppet.
(2) MeinErstesPuppet materialisiert sich.
(3) MeinErstesPuppet: Huuuuuhuuuuuuu, es hat funktioniert! *freu* *hüpf*
Die erste Zeile gibt an, dass der Server versucht, das Puppet zu laden. In der nächsten Zeile steht, dass dies erfolgreich war, denn das Puppet erscheint (diese Zeile erhalten auch alle anderen Leute im Raum.) Interessant ist die 3. Zeile: Das Puppet tut, was es soll, es sagt nämlich den Spruch, den wir oben im Programm einprogrammiert haben.

Hier noch eine kurze Erläuterung der im Hallo-Puppet benutzen Befehle:

Am Anfang eines jeden Puppets muss der Befehl "PUPPET <name>" stehen. (Wenn ich hier was in spitzen Klammern schreibe, dann heisst das, dass dieser Text durch was eigenes ersetzt werden muss, oben war dies der Name "Hallo-Puppet".) Der <name> des Puppets ist dazu da, damit man die eigenen Puppets besser auseinanderhalten kann. Ausserdem erscheint er im Info-Text des Puppets.

Der Befehl "PUPPET <name>" signalisiert dem Server, dass hier die Beschreibung des Puppets beginnt. (Manche Web-Space-Leute packen nämlich noch Werbung um das Puppet rum, und die wird dann ignoriert.)

Weiterhin sollte das Puppet mit dem Befehl "PUPPETEND" enden. Das ist zwar nicht zwingend erforderlich, aber wenn die Web-Space-Leute auch Werbung hinter das Puppet packen, muss man ihn doch verwenden.

Die drei Zeilen in der Mitte des Puppets beschreiben eine Action. Eine Action ist sowas wie eine Zusammenfassung einer Folge von Befehlen (hier allerdings nur ein einziger.) Actions beginnen immer mit "ACTION <name>" und enden mit "END". Unsere Action heisst "start". Das ist eine ganz besondere Action, die wird nämlich gleich ausgeführt, wenn das Puppet gestartet wurde.

Und was macht die letzte noch nicht betrachtete Zeile ">> Huuuuuhuuuuuuu, es hat funktioniert! *freu* *hüpf*" ? Naja, der Befehl ">> <text>" gibt einfach den <text> im Chatfenster aus. Die beiden Leerstellen vor diesem Befehl sind übrigens nur dort, damit die Beschreibung besser lesbar wird und könnten auch weggelassen werden. Die Leerstelle nach dem ">>" ist allerdings Pflicht.

Puppets die auf die Umwelt reagieren

So, jetzt wollen wir ein Puppet, dass "lol" sagt, wenn ich das Wort "Witz" benutze, das alle Leute begruesst, die vorbeischauen und das "berni ist super!" ausgibt, wenn ich ein Spiel gewonnen habe. Hier ist dieses Puppet (http://www.brettspielwelt.de/Other/Puppets/Puppi.prog):

PUPPET Mein-Zweites-Puppet

ACTION start
  WHEN KEYWORD Witz FROM berni DO lachen
  WHEN APPEAR * DO begruessen
  WHEN MATCH "berni*min.)" DO grats
END

ACTION lachen
  >> lol
END

ACTION begruessen
  >> Hallo [WHO],
  >> schön, dass du gekommen bist.
END

ACTION grats
  >> berni ist super!
END

PUPPETEND
Wenn ich das Puppet starte, erhalte ich:

-- Lade CommandPuppet Puppi.
Puppi materialisiert sich.
Puppi: Hallo berni,
Puppi: schön, dass du gekommen bist.
Puppi: Hallo Hallo-Puppet,
Puppi: schön, dass du gekommen bist.
Puppi: Hallo Puppi,
Puppi: schön, dass du gekommen bist.
berni: Das ist ein Witz
Puppi: lol
-- Umwerfend, wie berni das Spiel so souverän gewonnen hat (3 min.)
Puppi: berni ist super!
Was die drei Actions "lachen", "begruessen" und "grats" machen sollte inzwischen (fast) klar sein. Interessant ist die Action "start".

(1) WHEN KEYWORD Witz FROM berni DO lachen
(2) WHEN APPEAR * DO begruessen
(3) WHEN MATCH "berni*min.)" DO grats
Die erste Zeile besagt: Wenn das Schlüsselwort "Witz" fällt, und zwar von "berni", dann führe die Action "lachen" aus.

Die zweite Zeile besagt, dass wenn "*" den Raum betritt, die Action "begruessen" ausgeführt werden soll. Dabei steht "*" für all die Leute, zu dem nix anderes angegeben wurde (hier also alle).

Und bei der dritten Zeile habe ich ein bischen in die Trickkiste gelangt. Wenn nämlich jemand ein Spiel gewinnt, kommen ganz unterschiedliche Texte, denen aber allen gemeinsam ist, dass sie mit "min.)" enden. Weiterhin will ich nur, dass das Puppet reagiert, wenn ich es war, der gewonnen hat. Das kann ich mit einer MATCH-Anweisung. Die Matchanweisung besagt: Wenn die folgende Angabe auf die Chatzeile passt, dann führe die Action "grats" aus.

Was heisst es nun, dass die Angabe auf die Zeile passt? Eigentlich heisst das nur, dass jedesmal, wenn ein Sternchen in der Angabe vorkommt, beliebig viele Zeichen in der Chatzeile stehen dürfen.

Die Angabe "berni*min.)" passt also auf "berni das Spiel so souverän gewonnen hat (3 min.)". Dabei steht das Sternchen für " das Spiel so souverän gewonnen hat (3 ".

Oben habe ich geschrieben, dass alle anderen Actions (fast) klar sein sollten. Das fast, habe ich da dazu geschrieben, da es in "begruessen" noch eine Zeile gibt, die etwas seltsam ist:

>> Hallo [WHO],
Was bedeutet das [WHO]? Jedesmal, wenn durch eine WHEN-Anweisung eine Action ausgeführt wird, werden zuvor ein paar Dinge in Variablen gespeichert. Im Falle der WHEN APPEAR-Anweisung ist dies die Variable "WHO", in der der Name desjenigen gespeichert wird, der aufgetaucht ist. Schreibt man nun eckige Klammern um eine Variable, so wird anstelle dessen der Inhalt der Variablen ausgegeben.

Hat beispielsweise die Variable "WHO" den Wert "berni", so wird obige Zeile so interpretiert, als stünde dort:

>> Hallo berni,
WICHTIG: Es gibt Sonderfälle, in denen man mit der Variablen [WHO] wenig anfangen kann. In der Variablen [WHO] wird allgemen das erste Wort der Zeile gespeichert, die das Ereignis auslöst. Beim obigen Beispiel würde infolge der Zeile "-- unglaublich, berni das Spiel so souverän gewonnen hat (3 min.)" der Inhalt von [WHO] nicht "berni" sondern "--" sein. Ein Doppelpunkt am Ende des ersten Wortes wird übrigens automatisch entfernt.

Einstellungen des Puppets ändern

Auf Dauer ist es langweilig, wenn sich alle Puppets immer "materialisieren" etc. Man kann diese Texte aber auf die eigenen Puppets anpassen (http://www.brettspielwelt.de/Other/Puppets/Hund.prog):

PUPPET hund

LOGIN: " biegt um die Ecke."
LOGOUT: " rennt hinter einem Briefträger her."
APPEAR: " kommt angerannt und sagt:""Wuff""."
DISAPPEAR: " hat einen Knochen gesehn und rennt weg."

ACTION start
  >> /room 42
  SLEEP 2
  >> /hook berni
  SLEEP 2
  HARAKIRI
END

PUPPETEND
Wenn man das Puppet startet, erhält man:

-- Lade CommandPuppet Hund.
Hund biegt um die Ecke.
Hund hat einen Knochen gesehn und rennt weg.
[Hund] Hund kommt angerannt und sagt:"Wuff".
[Hund] Hund hat einen Knochen gesehn und rennt weg.
Hund kommt angerannt und sagt:"Wuff".
Hund rennt hinter einem Briefträger her.
Die Befehle "LOGIN: <string>" etc. setzen einfach die entsprechenden Texte. Diese Befehle müssen immer direkt nach dem PUPPET-Befehl

stehen. Ausserdem haben wir noch ein paar neue Befehle in der start-Action:

Nach ">> " kann anstelle von Text auch ein (BSW-)Befehl stehen, den das Puppet dann ausführen soll. Hier etwa ein "/room 42", was dazu führt, dass das Puppet in Raum 42 geht.

Der "SLEEP <sek>"-Befehl führt dazu, dass das Puppet <sek>-Sekunden lang nichts macht (=schläft). Gerade bei längeren Puppets macht es Sinn, diesen Befehl gelegentlich mal einzubauen, damit das Puppet sich nicht zu sehr ansträngen muss ;-)

Am Schluss steht dann noch der "HARAKIRI"-Befehl, der, wie der Name schon vermuten lässt, das Puppet Harakiri begehen lässt, oder weniger blumig ausgedrückt: Das Puppet loggt sich aus.

Ein Begrüßungs-Puppet

In vielen Städten wird man
am Marktplatz von Puppets begrüßt. Dabei unterscheiden vie meisten Puppets zwischen Bewohnern und Besuchern. Desweiteren werden die Begrüßungen zufällig variiert. (http://www.brettspielwelt.de/Other/Puppets/gruss.prog):

PUPPET Empfangsdame

LOGIN: " beginnt ihre Arbeit."
LOGOUT: " hat Feierabend."
APPEAR: " taucht an der Rezeption auf."
DISAPPEAR: " verschwindet unter der dem Tresen."
INFO: "Ich begrüße was das Zeug hält."

ACTION start
  WHEN APPEAR * DO gruss
END

ACTION gruss
  GETINFO [WHO]
  IF NOT EXISTS PUPPET   # ist die Variable überhaupt belegt?
    RETURN               # andernfalls wird die ACTION gruss abgebrochen
  IF NOT [PUPPET]        # ein Puppet wird nicht begrüßt
    BEGIN
      IF [CITYNR] == 4   # jemand aus OldFolksTown (C4)?
        DO OFT[RND2]     # zufällige Begrüßung für OFTler
      ELSE
        DO FREMD[RND2]   # zufällige Begrüßung für Fremde
    END
END

ACTION OFT0
  >> Guten Tag [TITEL] [WHO]! Schön sie zu sehen!
END

ACTION OFT1
  >> [TITEL] [WHO], es ist immer eine Freude sie zu begrüßen!
END

ACTION FREMD0
  >> Willkommen, [TITEL] [WHO]. Ich wünsche ihnen einen angenehmen Aufenthalt.
END

ACTION FREMD1
  >> [TITEL] [WHO] aus [CITY], Was verschafft uns die Ehre?
END

PUPPETEND

Hier noch eine Erklärung der benutzten Befehle:

  GETINFO <name>
liefert eine Reihe von Informationen über den Spieler <name>. Diese Informationen werden in speziellen Variablen gespeichert, die da wären:
CITY (Name der Stadt, aus der <name> kommt.)
CITYNR (Nummer der Stadt, aus der <name> kommt.)
LANGUAGE (de oder en, je nachdem, ob <name> deutsch oder englisch eingestellt hat)
PUPPET (TRUE oder FALSE, jen achdem, ob <name> ein Puppet ist, oder nicht)
PLAYING (TRUE oder FALSE, je nachdem, ob <name> gerade spielt, oder nicht)
TITEL (Der Titel von <name>, bsp. Baronin)
RANK (Der Rang von <name>, bsp. [W11])
SEX (Das Geschlecht, (m,w oder n))
TUTOR (TRUE oder FALSE, je nachdem, ob <name> ein Tutor ist)
REPORTER (TRUE oder FALSE, je nachdem, ob <name> ein Reporter ist)
AMT (Das Amt von <name>)
GILDENAMT (Das Gildenamt von <name>)
  EXISTS <var>

Liefert TRUE, falls die Variable mit dem Namen <var> einen Wert enthält, ansonsten FALSE. Hier ist zu beachten, dass es um die Variable, nicht um ihren Inhalt geht. Daher keine eckigen Klammern.

  RETURN
Beendet die Ausführung einer Action.

  IF <ausdruck>
    BEGIN
      <befehle1>
    END
  ELSE
    BEGIN
      <befehle2>
    END
Wenn der Ausdruck <ausdruck> wahr ist, werden die Befehle <befehle1> ausgeführt, ansonsten die Befehle <befehle2>. Auch hier kann BEGIN und END jeweils weggelassen werden, wenn es sich nur um einen Befehl handelt. Der ELSE-Teil ist auch optional.

  DO <action>
Ruft die Action mit dem Name <action> auf.

  [RND<zahl>]
Liefert eine Zufallszahl zwischen von 0 bis <zahl>-1, also [RND5] eine 0, 1, 2, 3 oder 4.
Ein Reset-Puppet

Dieses Puppet ist dafür gedacht, die Spielräume einer Stadt zu durchlaufen und die Spiele zurückzusetzen, wenn niemand anwesend ist. Wenn Spiele vor dem Ende verlassen wurde werden diese Räume nämlich in der SpielpartnerVermittlung nicht als frei angezeigt. Wenn Spieler anwesend sind, wird nicht zurückgesetzt, da diese evtl. schon gejoint, aber nicht gestartet haben könnten (http://www.brettspielwelt.de/Other/Puppets/reset.prog):

PUPPET Reset-Puppet

ACTION start
  WHEN ERROR DO error                  # Falls ein Fehler auftritt...
  WHEN TIMER 3600 DO rundgang          # stündlich wird der Rundgang ausgelöst.
END


ACTION rundgang
  SET RÄUME "0 2 3 6 7 8 10 11 17 19"  # die Liste mit Spiel-Räumen
  FOR RAUM IN [RÄUME] DO               # Bei jedem Durchlauf wird der nächste Raum
                                       #   aus der Liste in RAUM gespeichert.
    BEGIN
      >> /room C4-[RAUM]               # Das Puppet springt in den Raum.
      DO spielerzahl                   # Die Anzahl der anwesenden
                                       #   Spieler wird ermittelt.
      IF [x] == 0                      # ausser Puppet(s) ist niemand anwesend.
        >> /reset                      # Daraufhin wird das Spiel zurückgesetzt.
    END
  >> /room C4                          # Nach Abarbeitung der Raumliste
                                       #   geht es in diesen Raum.
  MASTERRESET                          # Hiermit wird das Puppet wieder
                                       #   in den Ursprungszustand zurückgesetz.  
END

ACTION spielerzahl
  GETWHO                               # liefert in [WHO] eine Liste mit
                                       # den Namen der Anwesenden
  SET x 0                              # die Anzahl x wird zu Beginn auf 0 gesetzt.
  FOR anwesender IN [WHO] DO           # Alle Namen aus der Liste
                                       #   werden abgearbeitet.
    BEGIN
      GETINFO [anwesender]             # Die Infos zum Namen werden gelesen.
      IF NOT [PUPPET]                  # Puppets werden nicht gezählt.
        EVAL x = [x] + 1               # Für jeden Spieler wird x erhöht.
    END
END

ACTION error
  >> Ein Fehler ist aufgetreten ([MESSAGE])
END

PUPPETEND
Hier noch eine Erklärung der benutzten Befehle:

  WHEN ERROR DO <action>
Falls ein Fehler auftritt (etwa weil das Puppet durch Null zu teilen versucht), so wird die Action <action> aufgerufen. Dort enthält [MESSAGE] eine Fehlermeldung (bestehend aus einer Java-Fehlermeldung und einer Zeilenangabe). Die Zeilenangabe beginnt übrigens immer erst ab der Zeile zu zählen, wo der PUPPET-Befehl steht.

  WHEN TIMER <int> DO <action>
Hiermit kann man nach Ablauf einer bestimmten Zeit <int> (in Sekunden) die Action <action> aufrufen. Der TIMER beginnt jedesmal neu zu zählen. Damit wird hier erreicht, dass der Rundgang nach einer Stunde ausgeführt wird. Um einen TIMER zu deaktivieren benutzt man den Befehl "IGNORE TIMER".

  SET <var> <wert>
Schreibt in die Variable <var> den Wert <wert>. <wert> kann auch eine Liste sein, wie die Raumliste im Beispiel.

  FOR <var> IN <liste> DO
    BEGIN
      <befehle>
    END
Für alle Elemente von <liste> wird <befehle> ausgeführt. Dabei enthält die Variable <var> das entsprechende Element. Handelt es sich bei <befehle> nur um einen Befehl kann man BEGIN und END weglassen.

  MASTERRESET
Das Puppet wird augenblicklich in seinen Ursprungszustand nach dem Aufrufen zurückgesetzt. Das bedeutet: Die Ausführung des Programmes wird abgebrochen, alle Ereignisse werden ignoriert, alle Variablen werden gelöscht und die ACTION start wird im Anschluss erneut ausgeführt.

  GETWHO
liefert in der Variablen [WHO] eine Liste aller im Raum Anwesenden (auch Puppets).

  EVAL <var> = <ausdruck>
Berechnet den Ausdruck <ausdruck> und schreibt das Ergebnis in die Variable <var>.
Ein Rechenkünstler

Als letztes Puppet möchte ich noch ein etwas umfangreicheres Vorstellen, welches in der Lage ist, phänomenale Rechenleistungen ;-) zu erbringen (http://www.brettspielwelt.de/Other/Puppets/Rechenkünstler.prog):

PUPPET Rechenkünstler

ACTION start
  WHEN ERROR DO error    # Falls ein Fehler auftritt...

  WHEN CHAT DO rechne    # Bei jeder Chatzeile wird "rechne" aufgerufen.
                         # Die Chatzeile wird dabei in der 
                         #   Variablen CHAT gespeichert.
END

ACTION rechne
  DO split               # Zerlege den Chat in die einzelnen Wörter.
  DO berechne            # Berechne das Ergebnis
  DO verrechnen          # Damits lustiger ist, verrechnet er sich gelegentlich...
  DO verkünden           # Verkündet das Ergebnis
  SLEEP 2                # etwas von der Ansträgung ausruhen
END

# Eingabe: der Chat in der Variablen CHAT
# Ausgabe: Die einzelnen Wörter in w0, w1, w2, etc. und die
#          Anzahl der Wörter in anzahl.

ACTION split
  SET x 0                 # x ist eine Hilfsvariable, die die Wörter mitzählt.
	                  # Wir beginnen mit dem Wort 0.
  FOR wort IN [CHAT] DO   # Alle Wörter der CHAT-Zeile der Reihe nach durchgehen
    BEGIN
      SET w[x] [wort]     # Das Wort in w0 etc. abspeichern
      EVAL x = [x] + 1    # Zu x eins dazu zählen.
    END
  SET anzahl [x]          # Die Anzahl setzen.
END

# Eingabe: Die einzelnen Wörter in w0, w1, etc. und deren Anzahl in anzahl.
# Ausgabe: Das berechnete Ergebnis in ergebnis.

ACTION berechne
  SET ergebnis [w1]                         # Ergebnis = 1. Zahl
  SET x 2                                   # Ab dem zweiten Zeichen auslesen
  UNSET op                                  # Bisher kein Operator
  WHILE [x] < [anzahl] DO
    BEGIN
      IF EXISTS op                          # Gibt es schon einen Operator?
        BEGIN
          IF [op] == plus                   # Ist dieser plus?

            EVAL ergebnis = [ergebnis] + [w[x]]
                                            # ... dann ausrechnen

          IF [op] == minus                  # Ist dieser minus?

            EVAL ergebnis = [ergebnis] - [w[x]]
                                            # ... dann ausrechnen

          IF ( [op] != plus ) AND ( [op] != minus )
                                            # Ist dieser weder plus noch minus?

            SET error "[op] kann ich nicht berechnen!"
                                            # Dann Fehlermeldung setzen.

          UNSET op
        END
      ELSE
        BEGIN                               # Kein Operator, dann diesen
          SET op [w[x]]                     # merken.
        END
      EVAL x = [x] + 1                      # Die Variable x erhöhen.
    END
  IF EXISTS op                              # Oh, die Zeile endet mit einem

    SET error "Nach [op] muss noch eine Zahl stehen!"
                                            # Operator, das darf nicht sein.

END

ACTION verrechnen
  IF NOT EXISTS error AND [RND3] == 0       # Etwa jedes dritte mal verrechnen
    EVAL ergebnis = [ergebnis] + [RND5] - 2 # Ergebnis um maximal 2 ändern
END

ACTION verkünden
  IF EXISTS error                           # Ist ein Fehler aufgetreten?
    >> [error]
  ELSE
    >> Das Ergebnis ist: [ergebnis]
  UNSET error                           # Fehlermeldung löschen.
END

ACTION error
  SET error "Oh, da stimmt irgendwas nicht: [MESSAGE]"
END

PUPPETEND
Und wenn man ihn startet erhält man:
Rechenkünstler materialisiert sich.
berni: 1 plus 1
Rechenkünstler: Das Ergebnis ist: 2
berni: 100 minus 64
Rechenkünstler: Das Ergebnis ist: 35
berni: 9 hoch 17
Rechenkünstler: hoch kann ich nicht berechnen!
berni: 7 plus
Rechenkünstler: Nach plus muss noch eine Zahl stehen!
berni: drei plus drei
Rechenkünstler: Oh, da stimmt irgendwas nicht:
    java.lang.NumberFormatException: drei in Zeile 44
Hier noch eine recht kurze Erklärung der benutzten Befehle:

  UNSET <var>
Löscht eine Variable

  WHILE <ausdruck>
    BEGIN
      <befehle>
    END
Solange der Ausdruck <ausdruck> richtig ist, werden die Befehle in <befehle> ausgeführt. Hier also, solange x<anzahl ist. Handelt es sich bei <befehle> nur um einen Befehl kann man BEGIN und END weglassen.


 Stand: 23.11.2002
Autoren: Yordan und berni,  Layout: Meijing