Paket für ILTB 2017 Download, Zip, ~20 MB (enthält Präsentation, Anleitung, Code)
Storyworld habe ich als Projekt für 10. Klassen am Gymnasium in Bayern im Rahmen des Fachs Informatik durchgeführt. Es geht dabei darum, dass Schülerinnen und Schüler für eine gemeinsame Fantasy-Spielwelt Geschichten schreiben, die in Form von Zustandsautomaten umgesetzt werden und die miteinander in Zusammenhang stehen können.
Vorgeschichte und Hintergrund
Eine beispielhafte Spielwelt
So sieht zum Beispiel eine Welt aus, die aus 6 Location-Objekten und 5 Connections besteht. Es ist Absicht, dass La Isla Bonita nicht so einfach zu erreichen ist.

Im Spiel kann man sich zwischen den Orten, die durch Kanten verbunden sind, bewegen. Der eigentliche Clou: An manchen Orten werden zusätzlich zu den Ortsbeschreibungen Geschichten oder Teile von Geschichten präsentiert.
Die Geschichte vom Froschkönig, klassisch und linear
Klassische Geschichten sind linear in dem Sinn, dass sie Absatz für Absatz gelesen werden und man auch nicht mehr zurückspringt. Das gilt auch dann, wenn nicht chronologisch erzählt wird, sondern mit Sprüngen, wie etwa in der Odyssee. Deutschlehrer verlangen manchmal von ihren Schülern, die Geschichte in einzelne Stationen aufzuteilen. Das sieht dann, etwas formalisiert, so aus:

Die Geschichte vom Froschkönig, als nichtlineares Spiel umgestaltet
Spiele sind of nicht-linear in dem Sinn, dass man zu vorherigen Zuständen zurück kann und bestimmte Phasen mehrfach durchläuft. Auch gibt es möglicherweise unterschiedliche Wege zwischen den Stationen. In der Informatik kann man so etwas als Zustandsautomat modellieren. Das sieht dann so aus, mit auslösenden Aktionen bei den Übergängen, ausgelösten Aktionen nach dem Schrägstrich, und zusätzlichen Bedingungen für den Zustandsübergang in eckigen Klammern.

Die zentralen Methoden der Geschichtenklassen
Bewegt man sich durch die Welt, erscheinen Reiter mit den zur Verfügung stehenden Geschichten. Wählt man eine davon, sieht man zum Beispiel dieses schlichte Beispiel – der Spieler befindet sich gerade im Hafen, könnte nach Innismund wechseln, und hat gerade die Geschichte “Das öde Haus” ausgewählt:

Zu diesem Zweck hat jede Geschichtenklasse zwei Methoden: createReport
und receiveMessage
. Die erste Methode wird aufgerufen, damit die Geschichte einen Bericht oder eine Beschreibung abgeben kann, möglichweise mit einer oder mehreren Optionen, die der Spieler danach wählen kann. Die zweite Methode gibt an, was geschieht, wenn der Spieler die jeweilige Option wählt, sie entspricht mehr oder weniger der klassischen Zustandsübergangsmethode:
class LeeresHaus extends SingleLocationStory {
public Report createReport() {
Report r = new Report("Ein leeres Haus.");
r.addOption("Du untersuchst es", "anschauen");
return r;
}
public void receiveMessage(String s) {
if (s.equals("anschauen")) {
sendMessage("Niemand zu Hause");
if(get("Neugier")<10)increase("Neugier", 1);
}
}
public String getName() { return "Das öde Haus"; }
public String startStoryAt() { return "Hafen"; }
public String getImageName() { return "resources/haus.png"; }
} |
class LeeresHaus extends SingleLocationStory {
public Report createReport() {
Report r = new Report(“Ein leeres Haus.”);
r.addOption(“Du untersuchst es”, “anschauen”);
return r;
}
public void receiveMessage(String s) {
if (s.equals(“anschauen”)) {
sendMessage(“Niemand zu Hause”);
if(get(“Neugier”)<10)increase(“Neugier”, 1);
}
}
public String getName() { return “Das öde Haus”; }
public String startStoryAt() { return “Hafen”; }
public String getImageName() { return “resources/haus.png”; }
}
Die Methode getName
gibt den Namen der Geschichte zurück, die für diese Unterklasse von Geschichten notwendige Methode startStoryAt
gibt den Ort zurück, an dem die Geschichte spielt, die optionale Methode getImageName
gibt den Ort und Namen eines passenden Bildes an.
Zu den typischen ausgelösten Aktionen bei der Zustandsübergangsmethode gehören set, increase, decrease
von beliebigen Spielwelt- oder Spielerattributen. (Wenn ein Attribut dieses Namens noch nicht existiert, wird es angelegt.) Den Wert dieses geschichtenübergreifenden Attributs kann man auch mit get
abfragen, um Anweisungen von bestimmten Bedingungen abhängig zu machen.
Außerdem kann man mit sendMessage
Text ausgeben. Daneben gibt es viele Möglichkeiten, den Zustand der Spielwelt zu ändern – man kann diese Geschichte an einen anderen ort platzieren, den Spieler auch, neue Orte oder Verbinungen zwischen Orten erschaffen. Häufig wird man auch ein Zustandsattribut in die eigene Geschichtenklasse einbauen und dessen Wert ändern. Von dem Wert dieses eigenen Zustandsattributs hängt dann auch der zurückgegebene Report ab, und die damit verbundenen Optionen – und schon hat man einen einfachen Zustandsautomaten.
– Wer die Trennung oder Doppelung der zwei Methoden createReport
und receiveMessage
unübersichtlich findet, für den gibt es Alternativen: In einem Anhang unten habe ich die verschiedene anderen Geschichtenklassen gegenübergestellt. Sie sind alle Unterklassen von AbstractStory
, und logisch alle äquivalent, bieten eben nur unterschiedliche Implementierungen.
ActionStory
Implementiert eine Geschichtenklasse das Interface ActionStory
, so muss es eine Methode void act()
geben. Diese Methode wird jedesmal aufgerufen, wenn der Spieler sich entscheidet, zu einem anderen Ort zu gehen. So kann man zum Beispiel einen Wanderer erschaffen, der sich bei jeder Bewegung des Spielers auch bewegt, zum Beispiel zu einem zufälligen Nachbarort.
Ausgelöste Aktionen
Typisch sind:
- set(String, int), increase(String, int), decrease(String, int)
- movePlayerTo(String)
- sendMessage(String)
- moveStoryTo(String) – das geht nur bei Objekten der Klasse SingleLocationStory und deren Unterklassen, da nur diese Geschichten an genau einem Ort spielen
- addLocation(Location), removeLocation(Location)
- addConnection(Location, Location, int), removeConnection(Location, Location)
- enter() und exit() – optional: nachdem man mit
enter()
eine Geschichte betreten hat, kann man nicht mehr zu einer anderen Geschichte oder einem anderen Ort wechseln, bis man zu einem Ende gekommen ist, das einen mit exit()
wieder aus der Geschichte entlässt (es gibt auch einen automaticEntryMode)
- introduceStory(AbstractStory)
- deleteStory(AbstractStory)
Bedingungen/Informationen über die Welt
- int get(String)
- boolean playerIsIn(String)
- String getPlayerLocationName()
- String getRandomNeighbourName(String)
- Location [] getAllNeighbours(String)
- Location getRandomLocation()
- int getTimeSinceLastReport()
Mögliche Geschichten
- Ein Wanderer, der zufällig herumzieht.
- Ein Tribble, das gefangen werden kann und dann verschwindet, sich ansonsten vermehrt, indem es als ActionStory in jedem Spielerzug mit einer gewissen Wahrscheinlichkeit neue Objekte der eigenen Klasse ins Spiel bringt. (Besser mit Obergrenze, da exponentielles Wachstum.)
- Eine klassische Kuriermission, bei der ein Gegenstand von Ort A nach Ort B gebracht werden muss und man dafür eine Belohnung erhält.
- Romeo und Julia: Man trägt Briefe zwischen beiden hin und her, bis beider Geschichte ein Ende findet.
- Eine Begleitmission, bei der man einen Passagier zu einem möglicherweise unbekannten Ort geleiten und abliefern muss. (Die letzten drei sind Beispiele für Geschichten, die an mehr als einem Ort spielen.)
- Ein bürokratisches Labyrinth, aus dem man herausfinden muss.
- Eine Fähre zwischen dem Hafen und La Isla Bonita – als Geschichte, die nur an einem Ort spielt, der aber mit
moveStoryTo
geändert wird, oder als Geschichte an zwei Orten. (An dem einen hat man die Fähre und die Möglichkeit, sie zu besteigen; am anderen wird man auf die abwesende Fähre hingewiesen. Die Fähre wechselt natürlich den Ort, entweder durch Spieleraktion oder selbstständig.)
- “Das öde Haus” von E.T.A. Hoffmann, umgeformt in eine Minigeschichte, mit viel Originalzitaten.
Langer Anhang: Varianten der zentralen Methoden in verschiedenen Unterklassen
Ich finde die Trennung in zwei Methoden createReport
und receiveMessage
übersichtlich und praktisch. Aber gerade wenn man die Geschichte mit einem Zustandsattribut umsetzt, gibt es unschöne Doppelungen im Code: Man braucht für beide Methoden ein ähnliches if-else- oder switch-Konstrukt, um den aktuellen Zustand abzufragen, von dem ja sowohl der Report als auch der Zustandsübergang abhängen. Wem das nicht gefällt, für den gibt es andere Möglichkeiten. Es folgt ein kurzer Überblick über die verschiedenen Geschichtenklassen. Eine Schüler-Klasse ist jeweils eine Unterklasse einer davon; die in diesem Fall zu überschreibenden Methoden sind jeweils farblich markiert.

AbstractStory
Das ist die allgemeinste Klasse. Unterklassen davon müssen einfach nur drei Methoden implementieren; wem das reicht, der kann die restlichen Klassen ignorieren:
String getName()
– das gilt für alle Geschichten-Klassen. Jede Geschichte braucht einen (eindeutigen) Namen.
Report createReport(String locationName)
– die Geschichte wird bei jeder Bewegung und jeder Entscheidung eines Spielers gebeten, einen Bericht abzuliefern. Dieser Bericht hängt vielleicht vom Zustand der Geschichte ab, aber ganz sicher vom Ort des Spielers, dessen Bezeichner als Argument übergeben wird. Wenn eine Geschichte in Innismund und in Das Schloss spielt, wird sie nur in diesen beiden Fällen einen Report abgeben, in allen anderen Fällen null
zurückgeben. So ein Null-Report wird dann nicht angezeigt.
Einem Report kann man Optionen hinzufügen, die dann als anklickbare Knöpfe erscheinen. Das geht unter anderem mit der Methode addOption(String, String)
. (Man kann auch Option-Objekte erzeugen, die dann mehr Möglichkeiten bieten.) Der erste String ist der Text, den der Spieler zu sehen kriegt; der zweite String ist die Nachricht, die an die Geschichtenklasse geschickt wird, damit auf die Wahl des Spielers reagiert werden kann – siehe folgende Methode.
void receiveMessage(String message)
– diese Methode wird aufgerufen, wenn der Spieler auf den entsprechenden Options-Knopf geklickt hat, dabei wird der String, der der Option als zweites Argument übergeben worden ist, zum Argument der receiveMessage(String)
-Methode gemacht. (Tatsächlich wird vorher die Methode receiveMessage(Option)
aufgerufen, wobei aus dem Option-Argument eine Reihe von Consequence-Objekten extrahiert und automatisch ausgeführt werden, bevor der Message-String extrahiert und an receiveMessage(String)
übergeben wird.)
import storyworld.*;
public class Fischer extends AbstractStory {
int zustand = 0;
public Fischer () { }
public String getName() { return "Der Angler"; }
public Report createReport(String locationName) {
if (!locationName.equals("Hafen")) return null; // die Geschichte gibt nur für einen Ort einen Report zurueck
if (zustand == 0) {
Report r = new Report("Ein Fischer sitzt am Kai und angelt.");
r.addOption("Du fragst ihn, wie es so läuft.", "ansprechen");
return r;
} else {
Report r = new Report("'Toll!' Der Angler erzählt dir eine lange Geschichte, wie er einmal fast den größten Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.");
r.addOption("Du dankst für das Gespräch und den Fisch.", "beenden");
return r;
}
}
public void receiveMessage(String s) {
if (zustand==0) {
if (s.equals("ansprechen") ) // ausloesende Aktion
{
if (get("Fische")<3) { // Bedingung des Zustandsuebergangs
zustand = 1;
increase("Fische", 1); // ausgeloeste Aktionen
} else {
sendMessage("Du hast schon genug Fische.");
}
}
}
else if (zustand==1) {
if (s.equals("beenden")) // ausloesende Aktion
{
zustand = 0;
}
}
}
} |
import storyworld.*;
public class Fischer extends AbstractStory {
int zustand = 0;
public Fischer () { }
public String getName() { return “Der Angler”; }
public Report createReport(String locationName) {
if (!locationName.equals(“Hafen”)) return null; // die Geschichte gibt nur für einen Ort einen Report zurueck
if (zustand == 0) {
Report r = new Report(“Ein Fischer sitzt am Kai und angelt.”);
r.addOption(“Du fragst ihn, wie es so läuft.”, “ansprechen”);
return r;
} else {
Report r = new Report(“ ‘Toll!’ Der Angler erzählt dir eine lange Geschichte, wie er einmal fast den größten Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.”);
r.addOption(“Du dankst für das Gespräch und den Fisch.”, “beenden”);
return r;
}
}
public void receiveMessage(String s) {
if (zustand==0) {
if (s.equals(“ansprechen”) ) // ausloesende Aktion
{
if (get(“Fische”)<3) { // Bedingung des Zustandsuebergangs
zustand = 1;
increase(“Fische”, 1); // ausgeloeste Aktionen
} else {
sendMessage(“Du hast schon genug Fische.”);
}
}
}
else if (zustand==1) {
if (s.equals(“beenden”)) // ausloesende Aktion
{
zustand = 0;
}
}
}
}
SingleLocationStory
Diese Klasse ist für Einsteiger gedacht; bei ihr muss die Methode createReport()
ohne Argument überschrieben werden. Diese Methode wird nämlich nur dann aufgerufen, wenn der Spieler sich an dem Ort befindet, an dem die Geschichte spielt, und der am Anfang durch startStoryAt()
festgelegt wird. Dafür kann die Geschichte aber auch nur an einem Ort gleichzeitig spielen. Dieser Ort kann mit moveStoryTo(String)
geändert werden.
Die Methoden getName()
und receiveMessage(String)
funktionieren wie oben.
import storyworld.*;
public class Fischer extends SingleLocationStory {
int zustand = 0;
public Fischer () { }
public String startStoryAt() { return "Hafen"; }
public String getName() { return "Der Angler"; }
public Report createReport() {
if (zustand == 0) {
Report r = new Report("Ein Fischer sitzt am Kai und angelt.");
r.addOption("Du fragst ihn, wie es so läuft.", "ansprechen");
return r;
} else {
Report r = new Report("'Toll!' Der Angler erzählt dir eine lange Geschichte, wie er einmal fast den größten Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.");
r.addOption("Du dankst für das Gespräch und den Fisch.", "beenden");
return r;
}
}
public void receiveMessage(String s) {
if (zustand==0) {
if (s.equals("ansprechen") ) // ausloesende Aktion
{
if (get("Fische")<3) { // Bedingung des Zustandsuebergangs
zustand = 1;
increase("Fische", 1); // ausgeloeste Aktionen
} else {
sendMessage("Du hast schon genug Fische.");
}
}
}
else if (zustand==1) {
if (s.equals("beenden")) // ausloesende Aktion
{
zustand = 0;
}
}
}
} |
import storyworld.*;
public class Fischer extends SingleLocationStory {
int zustand = 0;
public Fischer () { }
public String startStoryAt() { return “Hafen”; }
public String getName() { return “Der Angler”; }
public Report createReport() {
if (zustand == 0) {
Report r = new Report(“Ein Fischer sitzt am Kai und angelt.”);
r.addOption(“Du fragst ihn, wie es so läuft.”, “ansprechen”);
return r;
} else {
Report r = new Report(“ ‘Toll!’ Der Angler erzählt dir eine lange Geschichte, wie er einmal fast den größten Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.”);
r.addOption(“Du dankst für das Gespräch und den Fisch.”, “beenden”);
return r;
}
}
public void receiveMessage(String s) {
if (zustand==0) {
if (s.equals(“ansprechen”) ) // ausloesende Aktion
{
if (get(“Fische”)<3) { // Bedingung des Zustandsuebergangs
zustand = 1;
increase(“Fische”, 1); // ausgeloeste Aktionen
} else {
sendMessage(“Du hast schon genug Fische.”);
}
}
}
else if (zustand==1) {
if (s.equals(“beenden”)) // ausloesende Aktion
{
zustand = 0;
}
}
}
}
ShopStory
Eine Klasse, um Läden anzulegen, die Waren oder Gegenstände kaufen und verkaufen. Arbeitet viel mit Requirement
- und Consequence
-Objekten bei den Optionen. Überschrieben werden muss keine Methode, aber sinnvollerweise werden mit addItem
im Konstruktor Waren hinzugefügt, die der Laden kauft und verkauft.
import storyworld.*;
public class KleinerLaden extends ShopStory {
public String startStoryAt() { return "Innismund"; }
public String getName() { return "Der kleine Laden";}
public KleinerLaden() {
setDescription("Hier gibt es allerlei Dinge.");
addItem("Wasser",50, "Geld", 2, "Geld", 1); // 50 Wasser vorhanden, fuer 2 Geld zu kaufen, fuer 1 Geld an den Laden verkaufen
shopSells("Kräuterbaguette",5, "Geld",1000); // 1 Kraeuterbaguette für 1000 Geld kaufen (5 vorhanden)
shopBuys ("Seele",1, "Geld", 1000); // 1 Seele fuer 1000 Geld verkaufen
shopBuys ("Ansehen",10, "Geld", 1); // 10 Ansehen fuer 1 Geld verkaufen
shopBuys ("Tribble",1, "Geld", 1); // 1 Tribble fuer 1 Geld verkaufen
}
} |
import storyworld.*;
public class KleinerLaden extends ShopStory {
public String startStoryAt() { return “Innismund”; }
public String getName() { return “Der kleine Laden”;}
public KleinerLaden() {
setDescription(“Hier gibt es allerlei Dinge.”);
addItem(“Wasser”,50, “Geld”, 2, “Geld”, 1); // 50 Wasser vorhanden, fuer 2 Geld zu kaufen, fuer 1 Geld an den Laden verkaufen
shopSells(“Kräuterbaguette”,5, “Geld”,1000); // 1 Kraeuterbaguette für 1000 Geld kaufen (5 vorhanden)
shopBuys (“Seele”,1, “Geld”, 1000); // 1 Seele fuer 1000 Geld verkaufen
shopBuys (“Ansehen”,10, “Geld”, 1); // 10 Ansehen fuer 1 Geld verkaufen
shopBuys (“Tribble”,1, “Geld”, 1); // 1 Tribble fuer 1 Geld verkaufen
}
}
TestableStory
Eine Unterklasse, die speziell für das Projekt eingerichtet wurde; sie hat eine test-Methode, die eine Welt erzeugt und die Geschichte, die die Methode aufgerufen hat, in die Testwelt einbaut. Diese Methode muss von der Unterklasse am Ende des Konstruktors aufgerufen werden.
SimpleStory
Ist identisch zu SingleLocationStory
, nur mit der zusätzlichen Möglichkeit des Testens, indem am Ende des Konstruktors test()
aufgerufen wird.
MultiplaceStory
Entspricht letztlich der Klasse AbstractStory
– das heißt, die createReport
-Methode wird jetzt wieder bei jeder Location aufgerufen, dafür erhält sie jetzt wieder als String-Argument den Namen der aktuellen Location. Am Ende des Konstruktors wird test()
aufgerufen.
ModifiedSimpleStory
Vielleicht stört den einen oder anderen die Doppelung der if-Anweisung in den beiden Methoden. Deshalb bietet diese Klasse eine Methode Report receiveChoice(String message)
, die überschrieben werden muss. Sie ersetzt die beiden Methoden receiveMessage
und createReport
der Oberklasse, die demnach nicht überschrieben werden müssen.
Auch diese Methode wird nur dann aufgerufen, wenn der Spieler an dem einen Ort ist, an dem die Geschichte spielt. Man muss also nie einen Null-Report zurückgeben.
In dieser Klasse muss man zwar nur eine zentrale Methode schreiben, aber dese Methode enthält wieder zwei gro0ße if/switch-Blöcke: Zuerst einen für das Auswerten der Nachricht, dann einen für das Senden des aktuellen Zustands-Reports.
import storyworld.*;
public class Fischer extends ModifiedSimpleStory {
int zustand = 0;
public Fischer () { }
public String startStoryAt() { return "Hafen"; }
public String getName() { return "Der Angler"; }
public Report createReport(String s) {
//Teil 1
if (zustand==0) {
if (s.equals("ansprechen") ) // ausloesende Aktion
{
if (get("Fische")<3) { // Bedingung des Zustandsuebergangs
zustand = 1;
increase("Fische", 1); // ausgeloeste Aktionen
} else {
sendMessage("Du hast schon genug Fische.");
}
}
}
else if (zustand==1) {
if (s.equals("beenden")) // ausloesende Aktion
{
zustand = 0;
}
}
//Teil 2
if (zustand == 0) {
Report r = new Report("Ein Fischer sitzt am Kai und angelt.");
r.addOption("Du fragst ihn, wie es so läuft.", "ansprechen");
return r;
} else {
Report r = new Report("'Toll!' Der Angler erzählt dir eine lange Geschichte, wie er einmal fast den größten Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.");
r.addOption("Du dankst für das Gespräch und den Fisch.", "beenden");
return r;
}
}
} |
import storyworld.*;
public class Fischer extends ModifiedSimpleStory {
int zustand = 0;
public Fischer () { }
public String startStoryAt() { return “Hafen”; }
public String getName() { return “Der Angler”; }
public Report createReport(String s) {
//Teil 1
if (zustand==0) {
if (s.equals(“ansprechen”) ) // ausloesende Aktion
{
if (get(“Fische”)<3) { // Bedingung des Zustandsuebergangs
zustand = 1;
increase(“Fische”, 1); // ausgeloeste Aktionen
} else {
sendMessage(“Du hast schon genug Fische.”);
}
}
}
else if (zustand==1) {
if (s.equals(“beenden”)) // ausloesende Aktion
{
zustand = 0;
}
}
//Teil 2
if (zustand == 0) {
Report r = new Report(“Ein Fischer sitzt am Kai und angelt.”);
r.addOption(“Du fragst ihn, wie es so läuft.”, “ansprechen”);
return r;
} else {
Report r = new Report(“ ‘Toll!’ Der Angler erzählt dir eine lange Geschichte, wie er einmal fast den größten Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.”);
r.addOption(“Du dankst für das Gespräch und den Fisch.”, “beenden”);
return r;
}
}
}
ModifiedMultiplaceStory
Analog zu MultiplaceStory
wird hier Report receiveChoice(String message)
an jedem Ort des Spiels aufgerufen, man muss in dieser Methode also für die meisten Orte einen Null-Report zurückgeben. (Die Methoden receiveMessage
und createReport
entfallen wieder.)
Letztlich wie die Oberklasse, nur mit einem zusätzlichen if (!s.equals("Hafen")) return null;
YetAnotherModifiedSimpleStory
Diese Klasse ist für Leute, die die zwei if/switch-Blöcke stören: Hier gibt es nur einen davon. Da der verlangte Report ja erst am Schluss der Methode zurückgegeben werden kann, hat die zu implementierende Methode void createReport(String message)
hier keinen Rückgabetyp; dafür wird mit Aufrufen von setReport(Report)
der später zurückzugebende Report festgelegt.
import storyworld.*;
public class Fischer extends YetAnotherModifiedSimpleStory{
int zustand = 0;
public Fischer () { }
public String startStoryAt() { return "Hafen"; }
public String getName() { return "Der Angler"; }
public void receiveChoice(String s) {
if (zustand == 0) {
Report r = new Report("Ein Fischer sitzt am Kai und angelt.");
r.addOption("Du fragst ihn, wie es so läuft.", "ansprechen");
setReport(r);
if (s.equals("ansprechen")) {
if (get("Fische")<3) { // Bedingung des Zustandsuebergangs
zustand = 1;
increase("Fische", 1); // ausgeloeste Aktionen
} else {
sendMessage("Du hast schon genug Fische.");
}
}
} else {
Report r = new Report("'Toll!' Der Angler erzählt dir eine lange Geschichte, wie er einmal fast den größten Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.");
r.addOption("Du dankst für das Gespräch und den Fisch.", "beenden");
setReport(r);
if (s.equals("beenden")) {
zustand = 0;
}
}
}
} |
import storyworld.*;
public class Fischer extends YetAnotherModifiedSimpleStory{
int zustand = 0;
public Fischer () { }
public String startStoryAt() { return “Hafen”; }
public String getName() { return “Der Angler”; }
public void receiveChoice(String s) {
if (zustand == 0) {
Report r = new Report(“Ein Fischer sitzt am Kai und angelt.”);
r.addOption(“Du fragst ihn, wie es so läuft.”, “ansprechen”);
setReport(r);
if (s.equals(“ansprechen”)) {
if (get(“Fische”)<3) { // Bedingung des Zustandsuebergangs
zustand = 1;
increase(“Fische”, 1); // ausgeloeste Aktionen
} else {
sendMessage(“Du hast schon genug Fische.”);
}
}
} else {
Report r = new Report(“ ‘Toll!’ Der Angler erzählt dir eine lange Geschichte, wie er einmal fast den größten Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.”);
r.addOption(“Du dankst für das Gespräch und den Fisch.”, “beenden”);
setReport(r);
if (s.equals(“beenden”)) {
zustand = 0;
}
}
}
}
SimpleStoryChrisGraessl
Eine weitere, sehr elegante Methode. Hier muss man nur Report createReport()
überschreiben, die Methode receiveMessage(String)
entfällt beziehungsweise ist in der Oberklasse festgelegt. Das zweite Argument von:
addOption(String, String) |
addOption(String, String)
wird dabei als Methodenbezeichner verwendet. Setzt man diese Option:
addOption("Du isst den Kuchen.", "essen"); |
addOption(“Du isst den Kuchen.”, “essen”);
so wird, wenn der Spieler sich für sie entscheidet, die Methode void essen()
aufgerufen, die natürlich existieren muss.
import storyworld.*;
public class Fischer extends SimpleStoryChrisGraessl {
int zustand = 0;
public Fischer () { }
public String startStoryAt() { return "Hafen"; }
public String getName() { return "Der Angler"; }
public Report createReport() {
if (zustand == 0) {
Report r = new Report("Ein Fischer sitzt am Kai und angelt.");
r.addOption("Du fragst ihn, wie es so läuft.", "ansprechen");
return r;
} else {
Report r = new Report("'Toll!' Der Angler erzählt dir eine lange Geschichte, wie er einmal fast den größten Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.");
r.addOption("Du dankst für das Gespräch und den Fisch.", "beenden");
return r;
}
}
public void ansprechen() {
if (get("Fische")<3) { // Bedingung des Zustandsuebergangs
zustand = 1;
increase("Fische", 1); // ausgeloeste Aktionen
} else {
sendMessage("Du hast schon genug Fische.");
}
}
public void beenden() {
zustand = 0;
}
} |
import storyworld.*;
public class Fischer extends SimpleStoryChrisGraessl {
int zustand = 0;
public Fischer () { }
public String startStoryAt() { return “Hafen”; }
public String getName() { return “Der Angler”; }
public Report createReport() {
if (zustand == 0) {
Report r = new Report(“Ein Fischer sitzt am Kai und angelt.”);
r.addOption(“Du fragst ihn, wie es so läuft.”, “ansprechen”);
return r;
} else {
Report r = new Report(“ ‘Toll!’ Der Angler erzählt dir eine lange Geschichte, wie er einmal fast den größten Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.”);
r.addOption(“Du dankst für das Gespräch und den Fisch.”, “beenden”);
return r;
}
}
public void ansprechen() {
if (get(“Fische”)<3) { // Bedingung des Zustandsuebergangs
zustand = 1;
increase(“Fische”, 1); // ausgeloeste Aktionen
} else {
sendMessage(“Du hast schon genug Fische.”);
}
}
public void beenden() {
zustand = 0;
}
}