{"id":9033,"date":"2017-03-21T06:36:09","date_gmt":"2017-03-21T05:36:09","guid":{"rendered":"https:\/\/www.herr-rau.de\/wordpress\/?p=9033"},"modified":"2023-06-12T07:20:02","modified_gmt":"2023-06-12T05:20:02","slug":"storyworld-ein-java-projekt-fuer-die-10-klasse","status":"publish","type":"post","link":"https:\/\/www.herr-rau.de\/wordpress\/2017\/03\/storyworld-ein-java-projekt-fuer-die-10-klasse.htm","title":{"rendered":"Storyworld &#8211; ein Java-Projekt f\u00fcr die 10. Klasse"},"content":{"rendered":"<div style='text-align:right;'><small>(<a href='https:\/\/www.herr-rau.de\/wordpress\/2017\/03\/storyworld-ein-java-projekt-fuer-die-10-klasse.htm#comments'>11 Kommentare.<\/a>)<\/small> <\/div>\n<p><a href=\"https:\/\/www.herr-rau.de\/wordpress\/archiv\/ILTB-2017-Storyworld-Rau.zip\">Paket f\u00fcr ILTB 2017 Download, Zip, ~20 MB<\/a> (enth\u00e4lt Pr\u00e4sentation, Anleitung, Code)<\/p>\n\n\n\n<p>Storyworld habe ich als Projekt f\u00fcr 10. Klassen am Gymnasium in Bayern im Rahmen des Fachs Informatik durchgef\u00fchrt. Es geht dabei darum, dass Sch\u00fclerinnen und Sch\u00fcler f\u00fcr eine gemeinsame Fantasy-Spielwelt Geschichten schreiben, die in Form von Zustandsautomaten umgesetzt werden und die miteinander in Zusammenhang stehen k\u00f6nnen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Vorgeschichte und Hintergrund<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.herr-rau.de\/wordpress\/2016\/01\/was-ich-gerade-spiele-und-warum-sunless-sea.htm\">Wie ich darauf gekommen bin, Teil 1<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.herr-rau.de\/wordpress\/2016\/02\/sunless-sea-fortsetzung.htm\">Wie ich darauf gekommen bin, Teil 2<\/a><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Eine beispielhafte Spielwelt<\/h3>\n\n\n\n<p>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.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter\"><img loading=\"lazy\" decoding=\"async\" width=\"509\" height=\"415\" src=\"https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_miniwelt.png\" alt=\"Landkarte der Spielwelt\" class=\"wp-image-8974\" srcset=\"https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_miniwelt.png 509w, https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_miniwelt-150x122.png 150w, https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_miniwelt-300x245.png 300w\" sizes=\"auto, (max-width: 509px) 100vw, 509px\" \/><\/figure>\n\n\n\n<p>Im Spiel kann man sich zwischen den Orten, die durch Kanten verbunden sind, bewegen. Der eigentliche Clou: An manchen Orten werden zus\u00e4tzlich zu den Ortsbeschreibungen Geschichten oder Teile von Geschichten pr\u00e4sentiert.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Die Geschichte vom Froschk\u00f6nig, klassisch und linear<\/h3>\n\n\n\n<p>Klassische Geschichten sind linear in dem Sinn, dass sie Absatz f\u00fcr Absatz gelesen werden und man auch nicht mehr zur\u00fcckspringt. Das gilt auch dann, wenn nicht chronologisch erz\u00e4hlt wird, sondern mit Spr\u00fcngen, wie etwa in der Odyssee. Deutschlehrer verlangen manchmal von ihren Sch\u00fclern, die Geschichte in einzelne Stationen aufzuteilen. Das sieht dann, etwas formalisiert, so aus:<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter\"><img loading=\"lazy\" decoding=\"async\" width=\"578\" height=\"228\" src=\"https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_frosch1.png\" alt=\"Zustands\u00fcbergangsdiagramm Froschk\u00f6nig klassisch\" class=\"wp-image-8977\" srcset=\"https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_frosch1.png 578w, https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_frosch1-150x59.png 150w, https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_frosch1-300x118.png 300w\" sizes=\"auto, (max-width: 578px) 100vw, 578px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Die Geschichte vom Froschk\u00f6nig, als nichtlineares Spiel umgestaltet<\/h3>\n\n\n\n<p>Spiele sind of nicht-linear in dem Sinn, dass man zu vorherigen Zust\u00e4nden zur\u00fcck kann und bestimmte Phasen mehrfach durchl\u00e4uft. Auch gibt es m\u00f6glicherweise unterschiedliche Wege zwischen den Stationen. In der Informatik kann man so etwas als Zustandsautomat modellieren. Das sieht dann so aus, mit ausl\u00f6senden Aktionen bei den \u00dcberg\u00e4ngen, ausgel\u00f6sten Aktionen nach dem Schr\u00e4gstrich, und zus\u00e4tzlichen Bedingungen f\u00fcr den Zustands\u00fcbergang in eckigen Klammern.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter\"><img loading=\"lazy\" decoding=\"async\" width=\"579\" height=\"341\" src=\"https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_frosch2.png\" alt=\"Zustands\u00fcbergangsdiagramm Froschk\u00f6nig nichtlinear\" class=\"wp-image-8978\" srcset=\"https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_frosch2.png 579w, https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_frosch2-150x88.png 150w, https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_frosch2-300x177.png 300w\" sizes=\"auto, (max-width: 579px) 100vw, 579px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Die zentralen Methoden der Geschichtenklassen<\/h3>\n\n\n\n<figure class=\"wp-block-image aligncenter\"><img loading=\"lazy\" decoding=\"async\" width=\"632\" height=\"544\" src=\"https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_story1.png\" alt=\"Benutzeroberfl\u00e4che Spiel\" class=\"wp-image-8975\" srcset=\"https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_story1.png 632w, https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_story1-150x129.png 150w, https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_story1-300x258.png 300w\" sizes=\"auto, (max-width: 632px) 100vw, 632px\" \/><\/figure>\n\n\n\n<p>Bewegt man sich durch die Welt, erscheinen Reiter mit den zur Verf\u00fcgung stehenden Geschichten. W\u00e4hlt man eine davon, sieht man zum Beispiel dieses schlichte Beispiel &#8211; der Spieler befindet sich gerade im Hafen, k\u00f6nnte nach Innismund wechseln, und hat gerade die Geschichte &#8222;Das \u00f6de Haus&#8220; ausgew\u00e4hlt:<br><\/p>\n\n\n\n<p>Zu diesem Zweck hat jede Geschichtenklasse zwei Methoden: <code>createReport<\/code> und <code>receiveMessage<\/code>. Die erste Methode wird aufgerufen, damit die Geschichte einen Bericht oder eine Beschreibung abgeben kann, m\u00f6glichweise mit einer oder mehreren Optionen, die der Spieler danach w\u00e4hlen kann. Die zweite Methode gibt an, was geschieht, wenn der Spieler die jeweilige Option w\u00e4hlt, sie entspricht mehr oder weniger der klassischen Zustands\u00fcbergangsmethode:<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>class LeeresHaus extends SingleLocationStory {\n\n public Report createReport() { \n  Report r = new Report(\"Ein leeres Haus.\");\n  r.addOption(\"Du untersuchst es\", \"anschauen\");\n  return r;\n }\n\n public void receiveMessage(String s) {\n  if (s.equals(\"anschauen\")) {\n   sendMessage(\"Niemand zu Hause\");\n   if(get(\"Neugier\")&lt;10)increase(\"Neugier\", 1);\n  }\n }\n\n public String getName() { return \"Das \u00f6de Haus\"; }\n public String startStoryAt() { return \"Hafen\"; }\n public String getImageName() { return \"resources\/haus.png\"; }\n\n}<\/code><\/pre>\n\n\n\n<p>Die Methode <code>getName<\/code> gibt den Namen der Geschichte zur\u00fcck, die f\u00fcr diese Unterklasse von Geschichten notwendige Methode <code>startStoryAt<\/code> gibt den Ort zur\u00fcck, an dem die Geschichte spielt, die optionale Methode <code>getImageName<\/code> gibt den Ort und Namen eines passenden Bildes an.<\/p>\n\n\n\n<p>Zu den typischen ausgel\u00f6sten Aktionen bei der Zustands\u00fcbergangsmethode geh\u00f6ren <code>set, increase, decrease<\/code> von beliebigen Spielwelt- oder Spielerattributen. (Wenn ein Attribut dieses Namens noch nicht existiert, wird es angelegt.) Den Wert dieses geschichten\u00fcbergreifenden Attributs kann man auch mit <code>get<\/code> abfragen, um Anweisungen von bestimmten Bedingungen abh\u00e4ngig zu machen.<br>Au\u00dferdem kann man mit <code>sendMessage<\/code> Text ausgeben. Daneben gibt es viele M\u00f6glichkeiten, den Zustand der Spielwelt zu \u00e4ndern &#8211; man kann diese Geschichte an einen anderen ort platzieren, den Spieler auch, neue Orte oder Verbinungen zwischen Orten erschaffen. H\u00e4ufig wird man auch ein Zustandsattribut in die eigene Geschichtenklasse einbauen und dessen Wert \u00e4ndern. Von dem Wert dieses eigenen Zustandsattributs h\u00e4ngt dann auch der zur\u00fcckgegebene Report ab, und die damit verbundenen Optionen &#8211; und schon hat man einen einfachen Zustandsautomaten.<\/p>\n\n\n\n<p>&#8212; Wer die Trennung oder Doppelung der zwei Methoden <code>createReport<\/code> und <code>receiveMessage<\/code> un\u00fcbersichtlich findet, f\u00fcr den gibt es Alternativen: In einem Anhang unten habe ich die verschiedene anderen Geschichtenklassen gegen\u00fcbergestellt. Sie sind alle Unterklassen von <code>AbstractStory<\/code>, und logisch alle \u00e4quivalent, bieten eben nur unterschiedliche Implementierungen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">ActionStory<\/h3>\n\n\n\n<p>Implementiert eine Geschichtenklasse das Interface <code>ActionStory<\/code>, so muss es eine Methode <code>void act()<\/code> 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\u00e4lligen Nachbarort.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Ausgel\u00f6ste Aktionen<\/h3>\n\n\n\n<p>Typisch sind:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>set(String, int), increase(String, int), decrease(String, int)<\/li>\n\n\n\n<li>movePlayerTo(String)<\/li>\n\n\n\n<li>sendMessage(String)<\/li>\n\n\n\n<li>moveStoryTo(String) &#8211; das geht nur bei Objekten der Klasse SingleLocationStory und deren Unterklassen, da nur diese Geschichten an genau einem Ort spielen<\/li>\n\n\n\n<li>addLocation(Location), removeLocation(Location)<\/li>\n\n\n\n<li>addConnection(Location, Location, int), removeConnection(Location, Location)<\/li>\n\n\n\n<li>enter() und exit() &#8211; optional: nachdem man mit <code>enter()<\/code> 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 <code>exit()<\/code> wieder aus der Geschichte entl\u00e4sst (es gibt auch einen automaticEntryMode)<\/li>\n\n\n\n<li>introduceStory(AbstractStory)<\/li>\n\n\n\n<li>deleteStory(AbstractStory)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Bedingungen und Informationen \u00fcber die Welt<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>int get(String)<\/li>\n\n\n\n<li>boolean playerIsIn(String)<\/li>\n\n\n\n<li>String getPlayerLocationName()<\/li>\n\n\n\n<li>String getRandomNeighbourName(String)<\/li>\n\n\n\n<li>Location [] getAllNeighbours(String)<\/li>\n\n\n\n<li>Location getRandomLocation()<\/li>\n\n\n\n<li>int getTimeSinceLastReport()<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">M\u00f6gliche Geschichten<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Ein Wanderer, der zuf\u00e4llig herumzieht.<\/li>\n\n\n\n<li>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.)<\/li>\n\n\n\n<li>Eine klassische Kuriermission, bei der ein Gegenstand von Ort A nach Ort B gebracht werden muss und man daf\u00fcr eine Belohnung erh\u00e4lt.<\/li>\n\n\n\n<li>Romeo und Julia: Man tr\u00e4gt Briefe zwischen beiden hin und her, bis beider Geschichte ein Ende findet.<\/li>\n\n\n\n<li>Eine Begleitmission, bei der man einen Passagier zu einem m\u00f6glicherweise unbekannten Ort geleiten und abliefern muss. (Die letzten drei sind Beispiele f\u00fcr Geschichten, die an mehr als einem Ort spielen.)<\/li>\n\n\n\n<li>Ein b\u00fcrokratisches Labyrinth, aus dem man herausfinden muss.<\/li>\n\n\n\n<li>Eine F\u00e4hre zwischen dem Hafen und La Isla Bonita &#8211; als Geschichte, die nur an einem Ort spielt, der aber mit <code>moveStoryTo<\/code> ge\u00e4ndert wird, oder als Geschichte an zwei Orten. (An dem einen hat man die F\u00e4hre und die M\u00f6glichkeit, sie zu besteigen; am anderen wird man auf die abwesende F\u00e4hre hingewiesen. Die F\u00e4hre wechselt nat\u00fcrlich den Ort, entweder durch Spieleraktion oder selbstst\u00e4ndig.)<\/li>\n\n\n\n<li>&#8222;Das \u00f6de Haus&#8220; von E.T.A. Hoffmann, umgeformt in eine Minigeschichte, mit viel Originalzitaten.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Langer Anhang: Varianten der zentralen Methoden in verschiedenen Unterklassen<\/h3>\n\n\n\n<p>Ich finde die Trennung in zwei Methoden <code>createReport<\/code> und <code>receiveMessage<\/code> \u00fcbersichtlich und praktisch. Aber gerade wenn man die Geschichte mit einem Zustandsattribut umsetzt, gibt es unsch\u00f6ne Doppelungen im Code: Man braucht f\u00fcr beide Methoden ein \u00e4hnliches if-else- oder switch-Konstrukt, um den aktuellen Zustand abzufragen, von dem ja sowohl der Report als auch der Zustands\u00fcbergang abh\u00e4ngen. Wem das nicht gef\u00e4llt, f\u00fcr den gibt es andere M\u00f6glichkeiten. Es folgt ein kurzer \u00dcberblick \u00fcber die verschiedenen Geschichtenklassen. Eine Sch\u00fcler-Klasse ist jeweils eine Unterklasse einer davon; die in diesem Fall zu \u00fcberschreibenden Methoden sind jeweils farblich markiert.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter\"><a href=\"https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_story_classes_bunt.png\"><img loading=\"lazy\" decoding=\"async\" width=\"700\" height=\"579\" src=\"https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_story_classes_bunt-700x579.png\" alt=\"Klassendiagramm Story-Klassen\" class=\"wp-image-9040\" srcset=\"https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_story_classes_bunt-700x579.png 700w, https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_story_classes_bunt-150x124.png 150w, https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_story_classes_bunt-300x248.png 300w, https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_story_classes_bunt-660x545.png 660w, https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_story_classes_bunt.png 1400w\" sizes=\"auto, (max-width: 700px) 100vw, 700px\" \/><\/a><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">AbstractStory<\/h4>\n\n\n\n<p>Das ist die allgemeinste Klasse. Unterklassen davon m\u00fcssen einfach nur drei Methoden implementieren; wem das reicht, der kann die restlichen Klassen ignorieren:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>String getName()<\/code> &#8211; das gilt f\u00fcr alle Geschichten-Klassen. Jede Geschichte braucht einen (eindeutigen) Namen.<\/li>\n\n\n\n<li><code>Report createReport(String locationName)<\/code> &#8211; die Geschichte wird bei jeder Bewegung und jeder Entscheidung eines Spielers gebeten, einen Bericht abzuliefern. Dieser Bericht h\u00e4ngt vielleicht vom Zustand der Geschichte ab, aber ganz sicher vom Ort des Spielers, dessen Bezeichner als Argument \u00fcbergeben wird. Wenn eine Geschichte in Innismund und in Das Schloss spielt, wird sie nur in diesen beiden F\u00e4llen einen Report abgeben, in allen anderen F\u00e4llen <code>null<\/code> zur\u00fcckgeben. So ein Null-Report wird dann nicht angezeigt.<br>Einem Report kann man Optionen hinzuf\u00fcgen, die dann als anklickbare Kn\u00f6pfe erscheinen. Das geht unter anderem mit der Methode <code>addOption(String, String)<\/code>. (Man kann auch Option-Objekte erzeugen, die dann mehr M\u00f6glichkeiten 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 &#8211; siehe folgende Methode.<\/li>\n\n\n\n<li><code>void receiveMessage(String message)<\/code> &#8211; diese Methode wird aufgerufen, wenn der Spieler auf den entsprechenden Options-Knopf geklickt hat, dabei wird der String, der der Option als zweites Argument \u00fcbergeben worden ist, zum Argument der <code>receiveMessage(String)<\/code>-Methode gemacht. (Tats\u00e4chlich wird vorher die Methode <code>receiveMessage(Option)<\/code> aufgerufen, wobei aus dem Option-Argument eine Reihe von Consequence-Objekten extrahiert und automatisch ausgef\u00fchrt werden, bevor der Message-String extrahiert und an <code>receiveMessage(String)<\/code> \u00fcbergeben wird.)<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>import storyworld.*;\npublic class Fischer extends AbstractStory {\n  int zustand = 0;\n  public Fischer () { }\n  public String getName() { return \"Der Angler\"; }    \n\n  public Report createReport(String locationName) {\n    if (!locationName.equals(\"Hafen\")) return null; \/\/ die Geschichte gibt nur f\u00fcr einen Ort einen Report zurueck\n    if (zustand == 0) {\n      Report r = new Report(\"Ein Fischer sitzt am Kai und angelt.\");\n      r.addOption(\"Du fragst ihn, wie es so l\u00e4uft.\", \"ansprechen\");\n      return r;\n    }\n    else {\n      Report r = new Report(\"'Toll!' Der Angler erz\u00e4hlt dir eine lange Geschichte, wie er einmal fast den gr\u00f6\u00dften Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.\");\n      r.addOption(\"Du dankst f\u00fcr das Gespr\u00e4ch und den Fisch.\", \"beenden\"); \n      return r;\n    }\n  }\n\n  public void receiveMessage(String s) {\n    if (zustand==0) {\n      if (s.equals(\"ansprechen\") ) \/\/ ausloesende Aktion\n      { \n        if (get(\"Fische\")&lt;3) { \/\/ Bedingung des Zustandsuebergangs \n          zustand = 1; \n          increase(\"Fische\", 1); \/\/ ausgeloeste Aktionen\n        }\n        else {\n          sendMessage(\"Du hast schon genug Fische.\"); \n        }\n      }\n    }\n    else if (zustand==1) {\n      if (s.equals(\"beenden\")) \/\/ ausloesende Aktion\n      {\n        zustand = 0; \n      }\n    }\n  }    \n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">SingleLocationStory<\/h4>\n\n\n\n<p>Diese Klasse ist f\u00fcr Einsteiger gedacht; bei ihr muss die Methode <code>createReport()<\/code> ohne Argument \u00fcberschrieben werden. Diese Methode wird n\u00e4mlich nur dann aufgerufen, wenn der Spieler sich an dem Ort befindet, an dem die Geschichte spielt, und der am Anfang durch <code>startStoryAt()<\/code> festgelegt wird. Daf\u00fcr kann die Geschichte aber auch nur an einem Ort gleichzeitig spielen. Dieser Ort kann mit <code>moveStoryTo(String)<\/code> ge\u00e4ndert werden.<br>Die Methoden <code>getName()<\/code> und <code>receiveMessage(String)<\/code> funktionieren wie oben.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>import storyworld.*;\npublic class Fischer extends SingleLocationStory {\n  int zustand = 0;\n  public Fischer () { }\n  public String startStoryAt() { return \"Hafen\"; }\n  public String getName() { return \"Der Angler\"; }    \n\n  public Report createReport() {        \n    if (zustand == 0) {\n      Report r = new Report(\"Ein Fischer sitzt am Kai und angelt.\");\n      r.addOption(\"Du fragst ihn, wie es so l\u00e4uft.\", \"ansprechen\");\n      return r;\n    }\n    else {\n      Report r = new Report(\"'Toll!' Der Angler erz\u00e4hlt dir eine lange Geschichte, wie er einmal fast den gr\u00f6\u00dften Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.\");\n      r.addOption(\"Du dankst f\u00fcr das Gespr\u00e4ch und den Fisch.\", \"beenden\"); \n      return r;\n    }\n  }\n\n  public void receiveMessage(String s) {\n    if (zustand==0) {\n      if (s.equals(\"ansprechen\") ) \/\/ ausloesende Aktion\n      { \n        if (get(\"Fische\")&lt;3) { \/\/ Bedingung des Zustandsuebergangs \n          zustand = 1; \n          increase(\"Fische\", 1); \/\/ ausgeloeste Aktionen\n        }\n        else {\n          sendMessage(\"Du hast schon genug Fische.\"); \n        }\n      }\n    }\n    else if (zustand==1) {\n      if (s.equals(\"beenden\")) \/\/ ausloesende Aktion\n      {\n        zustand = 0; \n      }\n    }\n  }    \n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">ShopStory<\/h4>\n\n\n\n<p>Eine Klasse, um L\u00e4den anzulegen, die Waren oder Gegenst\u00e4nde kaufen und verkaufen. Arbeitet viel mit <code>Requirement<\/code>&#8211; und <code>Consequence<\/code>-Objekten bei den Optionen. \u00dcberschrieben werden muss keine Methode, aber sinnvollerweise werden mit <code>addItem<\/code> im Konstruktor Waren hinzugef\u00fcgt, die der Laden kauft und verkauft.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>import storyworld.*;\n\npublic class KleinerLaden extends ShopStory {\n  public String startStoryAt() {  return \"Innismund\"; }\n  public String getName() { return \"Der kleine Laden\";}\n  public KleinerLaden() {\n    setDescription(\"Hier gibt es allerlei Dinge.\");        \n    addItem(\"Wasser\",50, \"Geld\", 2, \"Geld\", 1); \/\/ 50 Wasser vorhanden, fuer 2 Geld zu kaufen, fuer 1 Geld an den Laden verkaufen \n    shopSells(\"Kr\u00e4uterbaguette\",5, \"Geld\",1000); \/\/ 1 Kraeuterbaguette f\u00fcr 1000 Geld kaufen (5 vorhanden)             \n    shopBuys (\"Seele\",1, \"Geld\", 1000); \/\/ 1 Seele fuer 1000 Geld verkaufen\n    shopBuys (\"Ansehen\",10, \"Geld\", 1); \/\/ 10 Ansehen fuer 1 Geld verkaufen\n    shopBuys (\"Tribble\",1, \"Geld\", 1); \/\/ 1 Tribble fuer 1 Geld verkaufen\n  }\n}\n<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">TestableStory<\/h4>\n\n\n\n<p>Eine Unterklasse, die speziell f\u00fcr 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.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">SimpleStory<\/h4>\n\n\n\n<p>Ist identisch zu <code>SingleLocationStory<\/code>, nur mit der zus\u00e4tzlichen M\u00f6glichkeit des Testens, indem am Ende des Konstruktors <code>test()<\/code> aufgerufen wird.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">MultiplaceStory<\/h4>\n\n\n\n<p>Entspricht letztlich der Klasse <code>AbstractStory<\/code> &#8211; das hei\u00dft, die <code>createReport<\/code>-Methode wird jetzt wieder bei <em>jeder<\/em> Location aufgerufen, daf\u00fcr erh\u00e4lt sie jetzt wieder als String-Argument den Namen der aktuellen Location. Am Ende des Konstruktors wird <code>test()<\/code> aufgerufen.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">ModifiedSimpleStory<\/h4>\n\n\n\n<p>Vielleicht st\u00f6rt den einen oder anderen die Doppelung der if-Anweisung in den beiden Methoden. Deshalb bietet diese Klasse eine Methode <code>Report receiveChoice(String message)<\/code>, die \u00fcberschrieben werden muss. Sie ersetzt die beiden Methoden <code>receiveMessage<\/code> und <code>createReport<\/code> der Oberklasse, die demnach nicht \u00fcberschrieben werden m\u00fcssen.<br>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\u00fcckgeben.<br>In dieser Klasse muss man zwar nur eine zentrale Methode schreiben, aber dese Methode enth\u00e4lt wieder zwei gro\u00dfe if\/switch-Bl\u00f6cke: Zuerst einen f\u00fcr das Auswerten der Nachricht, dann einen f\u00fcr das Senden des aktuellen Zustands-Reports.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>import storyworld.*;\npublic class Fischer extends ModifiedSimpleStory {\n  int zustand = 0;\n  public Fischer () { }\n  public String startStoryAt() { return \"Hafen\"; }\n  public String getName() { return \"Der Angler\"; }    \n\n  public Report createReport(String s) {\n    \/\/Teil 1   \n    if (zustand==0) {\n      if (s.equals(\"ansprechen\") ) \/\/ ausloesende Aktion\n      { \n        if (get(\"Fische\")&lt;3) { \/\/ Bedingung des Zustandsuebergangs \n          zustand = 1; \n          increase(\"Fische\", 1); \/\/ ausgeloeste Aktionen\n        }\n        else {\n          sendMessage(\"Du hast schon genug Fische.\"); \n        }\n      }\n    }\n    else if (zustand==1) {\n      if (s.equals(\"beenden\")) \/\/ ausloesende Aktion\n      {\n        zustand = 0; \n      }\n    }\n    \/\/Teil 2\n    if (zustand == 0) {\n      Report r = new Report(\"Ein Fischer sitzt am Kai und angelt.\");\n      r.addOption(\"Du fragst ihn, wie es so l\u00e4uft.\", \"ansprechen\");\n      return r;\n    } \n    else {\n      Report r = new Report(\"'Toll!' Der Angler erz\u00e4hlt dir eine lange Geschichte, wie er einmal fast den gr\u00f6\u00dften Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.\");\n      r.addOption(\"Du dankst f\u00fcr das Gespr\u00e4ch und den Fisch.\", \"beenden\"); \n      return r;\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">ModifiedMultiplaceStory<\/h4>\n\n\n\n<p>Analog zu <code>MultiplaceStory<\/code> wird hier <code>Report receiveChoice(String message)<\/code> an jedem Ort des Spiels aufgerufen, man muss in dieser Methode also f\u00fcr die meisten Orte einen Null-Report zur\u00fcckgeben. (Die Methoden <code>receiveMessage<\/code> und <code>createReport<\/code> entfallen wieder.)<\/p>\n\n\n\n<p>Letztlich wie die Oberklasse, nur mit einem zus\u00e4tzlichen <code>if (!s.equals(\"Hafen\")) return null; <\/code><\/p>\n\n\n\n<h4 class=\"wp-block-heading\">YetAnotherModifiedSimpleStory<\/h4>\n\n\n\n<p>Diese Klasse ist f\u00fcr Leute, die die zwei if\/switch-Bl\u00f6cke st\u00f6ren: Hier gibt es nur einen davon. Da der verlangte Report ja erst am Schluss der Methode zur\u00fcckgegeben werden kann, hat die zu implementierende Methode <code>void createReport(String message)<\/code> hier <em>keinen<\/em> R\u00fcckgabetyp; daf\u00fcr wird mit Aufrufen von <code>setReport(Report)<\/code> der sp\u00e4ter zur\u00fcckzugebende Report festgelegt.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>import storyworld.*;\npublic class Fischer extends YetAnotherModifiedSimpleStory {\n  int zustand = 0;\n  public Fischer () { }\n  public String startStoryAt() { return \"Hafen\"; }\n  public String getName() { return \"Der Angler\"; }    \n\n  public void receiveChoice(String s) {        \n    if (zustand == 0) {\n      Report r = new Report(\"Ein Fischer sitzt am Kai und angelt.\");\n      r.addOption(\"Du fragst ihn, wie es so l\u00e4uft.\", \"ansprechen\");\n      setReport(r);\n      if (s.equals(\"ansprechen\")) {\n        if (get(\"Fische\")&lt;3) \/\/ Bedingung des Zustandsuebergangs \n        {\n          zustand = 1; \n          increase(\"Fische\", 1); \/\/ ausgeloeste Aktionen\n        } \n        else {\n          sendMessage(\"Du hast schon genug Fische.\"); \n        }\n      }\n    }\n    else {\n      Report r = new Report(\"'Toll!' Der Angler erz\u00e4hlt dir eine lange Geschichte, wie er einmal fast den gr\u00f6\u00dften Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.\");\n      r.addOption(\"Du dankst f\u00fcr das Gespr\u00e4ch und den Fisch.\", \"beenden\"); \n      setReport(r);\n      if (s.equals(\"beenden\")) {\n        zustand = 0;\n      }\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">SimpleStoryChrisGraessl<\/h4>\n\n\n\n<p>Eine weitere, sehr elegante Methode. Hier muss man nur <code>Report createReport()<\/code> \u00fcberschreiben, die Methode <code>receiveMessage(String)<\/code> entf\u00e4llt beziehungsweise ist in der Oberklasse festgelegt. Das zweite Argument von:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>addOption(String, String)<\/code><\/pre>\n\n\n\n<p>wird dabei als Methodenbezeichner verwendet. Setzt man diese Option:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>addOption(\"Du isst den Kuchen.\", \"essen\");<\/code><\/pre>\n\n\n\n<p>so wird, wenn der Spieler sich f\u00fcr sie entscheidet, die Methode <code>void essen()<\/code> aufgerufen, die nat\u00fcrlich existieren muss.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>import storyworld.*;\npublic class Fischer extends SimpleStoryChrisGraessl {\n  int zustand = 0;\n  public Fischer () { }\n  public String startStoryAt() { return \"Hafen\"; }\n  public String getName() { return \"Der Angler\"; }    \n\n  public Report createReport() {        \n    if (zustand == 0) {\n      Report r = new Report(\"Ein Fischer sitzt am Kai und angelt.\");\n      r.addOption(\"Du fragst ihn, wie es so l\u00e4uft.\", \"ansprechen\");\n      return r;\n    }\n    else {\n      Report r = new Report(\"'Toll!' Der Angler erz\u00e4hlt dir eine lange Geschichte, wie er einmal fast den gr\u00f6\u00dften Fisch gefangen hat, denn es je zu sehen gab, und gibt dir einen Fisch.\");\n      r.addOption(\"Du dankst f\u00fcr das Gespr\u00e4ch und den Fisch.\", \"beenden\"); \n      return r;\n    }\n  }\n\n  public void ansprechen() {\n    if (get(\"Fische\")&lt;3) { \/\/ Bedingung des Zustandsuebergangs \n      zustand = 1; \n      increase(\"Fische\", 1); \/\/ ausgeloeste Aktionen\n    }\n    else {\n      sendMessage(\"Du hast schon genug Fische.\"); \n    }\n  }\n\n  public void beenden() {\n    zustand = 0; \n  }    \n}<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>(11 Kommentare.) Paket f\u00fcr ILTB 2017 Download, Zip, ~20 MB (enth\u00e4lt Pr\u00e4sentation, Anleitung, Code) Storyworld habe ich als Projekt f\u00fcr 10. Klassen am Gymnasium in Bayern im Rahmen des Fachs Informatik durchgef\u00fchrt. Es geht dabei darum, dass Sch\u00fclerinnen und Sch\u00fcler f\u00fcr eine gemeinsame Fantasy-Spielwelt Geschichten schreiben, die in Form von Zustandsautomaten umgesetzt werden und die [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":9040,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[25],"tags":[227,233],"class_list":["post-9033","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-informatik","tag-informatik","tag-programmierprojekte"],"jetpack_featured_media_url":"https:\/\/www.herr-rau.de\/wordpress\/archiv\/storyworld_story_classes_bunt.png","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/www.herr-rau.de\/wordpress\/wp-json\/wp\/v2\/posts\/9033","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.herr-rau.de\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.herr-rau.de\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.herr-rau.de\/wordpress\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.herr-rau.de\/wordpress\/wp-json\/wp\/v2\/comments?post=9033"}],"version-history":[{"count":3,"href":"https:\/\/www.herr-rau.de\/wordpress\/wp-json\/wp\/v2\/posts\/9033\/revisions"}],"predecessor-version":[{"id":58225,"href":"https:\/\/www.herr-rau.de\/wordpress\/wp-json\/wp\/v2\/posts\/9033\/revisions\/58225"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.herr-rau.de\/wordpress\/wp-json\/wp\/v2\/media\/9040"}],"wp:attachment":[{"href":"https:\/\/www.herr-rau.de\/wordpress\/wp-json\/wp\/v2\/media?parent=9033"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.herr-rau.de\/wordpress\/wp-json\/wp\/v2\/categories?post=9033"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.herr-rau.de\/wordpress\/wp-json\/wp\/v2\/tags?post=9033"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}