Funktionsprogrammierung und Textabenteuer


14

Dies ist größtenteils eine theoretische Frage zu FP, aber ich nehme Textabenteuer (wie Old-School-Zork), um meinen Standpunkt zu veranschaulichen. Ich würde gerne wissen, wie Sie eine Stateful-Simulation mit FP modellieren würden.

Text-Abenteuer scheinen wirklich OOP zu erfordern. Zum Beispiel sind alle "Räume" Instanzen einer RoomKlasse, Sie können eine Basisklasse Itemund Schnittstellen wie Item<Pickable>für Dinge haben, die Sie tragen können und so weiter.

Die Weltmodellierung in FP funktioniert anders, insbesondere wenn Sie Unveränderlichkeit in einer Welt erzwingen möchten, die im Verlauf des Spiels mutieren muss (Objekte werden bewegt, Feinde werden besiegt, die Punktzahl wächst, der Spieler ändert seinen Standort). Ich stelle mir ein einziges großes Objekt vor World, das alles bietet: Welche Räume können Sie erkunden, wie sind sie miteinander verbunden, was der Spieler trägt, welche Hebel wurden betätigt?

Ich denke, dass ein reiner Ansatz wäre, dieses große Objekt grundsätzlich an eine beliebige Funktion zu übergeben und es von ihnen zurückgeben zu lassen (möglicherweise modifiziert). Zum Beispiel habe ich eine moveToRoomFunktion, die es bekommt Worldund mit World.player.locationdem neuen Raum verändert zurückgibt , World.rooms[new_room].visited = Trueund so weiter.

Auch wenn dies der "korrektere" Weg ist, scheint es die Reinheit deswegen zu erzwingen. Abhängig von der Programmiersprache Worldkann das Hin- und Herbewegen dieses möglicherweise sehr großen Objekts teuer sein. Außerdem muss jede Funktion möglicherweise Zugriff auf ein beliebiges WorldObjekt haben. Zum Beispiel kann ein Raum zugänglich sein oder nicht, abhängig von einem Hebel, der in einem anderen Raum ausgelöst wurde, weil er möglicherweise überflutet ist. Wenn der Spieler jedoch eine Schwimmweste trägt, kann er ihn trotzdem betreten. Ein Monster kann aggressiv sein oder nicht, je nachdem, ob der Spieler seinen Cousin in einem anderen Raum getötet hat. Dies bedeutet , dass die roomCanBeEnteredFunktion zugreifen muss World.player.invetoryund World.rooms, describeMonsterBedürfnisse Zugang World.monstersund so weiter (im Grunde, Sie müssendie ganze Ladung herumreichen). Das scheint mir wirklich eine globale Variable zu sein, auch wenn dies alles andere als ein guter Programmierstil ist, besonders in FP.

Wie würden Sie dieses Problem lösen?


4
"Abhängig von der Programmiersprache kann das Hin- und Herbewegen dieses möglicherweise sehr großen World-Objekts teuer sein." Es wird wahrscheinlich als Referenz übergeben. "Außerdem muss jede Funktion möglicherweise Zugriff auf ein beliebiges Weltobjekt haben." Ich finde es schwierig zu glauben, dass jede Funktion Zugriff auf den gesamten Stand des Spiels benötigt.
Doval

2
Ich denke, Chris Martens Forschung wäre interessant, sie soll zeigen, wie man interaktive Fiktion in deklarativen Sprachen schön macht. github.com/chrisamaphone/interactive-lp
Daniel Gratzer

2
Vielleicht möchten Sie einen Blick auf diesen Blog werfen, in dem die Vorgehensweise des Autors bei der funktionalen Programmierung eines solchen Spiels beschrieben wird. Dieser Beitrag ist insbesondere ganz auf den Punkt.
Gallais

3
Ich muss mich fragen, ob diese Frage @ EricLipperts spätere Entscheidung beeinflusst hat, eine Reihe von Artikeln über die Implementierung (von Teilen) einer Z-Maschine in Ocaml zu schreiben ...?
Jules

1
@Jules Ein Link zum Start dieser Serie für Interessierte: ericlippert.com/2016/02/01/west-of-house
KChaloux

Antworten:


4

Beachten Sie, dass funktionale Sprachen Datenstrukturen und getrennte Funktionen anstelle von Objekten verwenden. Zum Beispiel hätten Sie stattdessen eine Reihe von Räumen und eine Liste von Inventargegenständen als Welt.

Idealerweise beschränken Sie die Datenmenge, die Sie Funktionen zur Verfügung stellen, auf die tatsächlich erforderliche Menge, anstatt die gesamte Welt zu durchlaufen (sagen wir, Sie extrahieren einen einzelnen relevanten Raum aus Ihrer Welt; es kann natürlich schwierig sein, vollständig voneinander abhängige Welten zu finden) trennen). Das Ergebnis würde wieder in die Datenstruktur der Welt eingehen und einen neuen Staat schaffen. Sie können den Zustand nicht modellieren, ohne den Zustand zu verwenden. Wie Sie sagen, erfordern einige Dinge von Natur aus eine Mutation.

Die meisten praktischen funktionalen Sprachen bieten die Möglichkeit, Mutationen entweder direkt (z. B. die ST-Monade in Haskell oder Transienten in Clojure) oder effizient zu simulieren (häufig durch Wiederverwendung unveränderter Teile der Datenstruktur). (Die Standarddatenstrukturen von Clojure sind hier ein gutes Beispiel.) ). So oder so bleibt die Reinheit erhalten.

Da das Ausmaß des Zustands, der mutiert werden muss, begrenzt zu sein scheint, würde ich mich nicht zu sehr um Leistungsprobleme kümmern und den (möglicherweise bereits optimierten) naiven funktionalen Ansatz wählen.

Eine andere Option, die ich gesehen habe, wäre, nur Anweisungen zurückzugeben, um einen Teil der Welt von Ihren individuellen Funktionen zu ändern, und dann Ihre Welt gemäß diesen zu aktualisieren. Eine Reihe von Blogposts, in denen dies beschrieben wird, finden Sie unter http://prog21.dadgum.com/23.html .

In beiden Antworten geht es mehr darum, Veränderungen zu organisieren, als Ihre gesamte Welt an Funktionen zu übergeben, da eine vollkommen voneinander abhängige nicht per Definition segmentiert werden kann - aber versuchen Sie dies so gut wie möglich in Ihrem Fall, was nicht nur der Fall ist funktional, aber auch gute Praxis.


0

Ich selbst würde auf jeden Fall die Fähigkeit der fraglichen Sprache untersuchen, auf irgendeine Form von Datenbank zuzugreifen. Die meisten Ereignisse, die gleichzeitig den Zustand der Welt verändern, werden einfach auf der Festplatte aufgezeichnet und wirken sich nicht auf den aktuellen Player im aktuellen Raum aus (außerhalb besonderer Umstände wie Explosionen oder in einem MMO, die Türen öffnen) aus der Ferne, Rufe anderer Spieler, etc).

Als solches muss der aktuelle Kunde sich des RoomObjekts und der Dinge, die es direkt betreffen , nur bewusst sein . noticableEventsOutsideRoomkönnte dann einfach eine Unterklasse sein, Roomdie von den letzten Änderungen an der Datenbank betroffen ist, und Ihr Spielobjekt ist viel kleiner geworden.


Ich verstehe, dass dieser Ansatz nicht viel dazu beiträgt, lokale Ereignisse zu finden oder auszulösen (z. B. Agro auf Mobs in der Nähe), aber ich war in der Vergangenheit dafür bekannt, Datenbanken zu missbrauchen ... Ich würde wahrscheinlich nur einen Anruf an update mobs set agro=1 where distance<5und sein fertig damit. Vielleicht ist das nicht die beste Vorgehensweise, aber es entspricht meinen Zwecken. Bezüglich der Pfadfindung über eine Datenbank könnte man immer den Dijkstra-Algorithmus mit dem kürzesten Pfad verwenden ...
Ayelis,

0

Die wirkliche Lösung besteht nicht darin, alles auf einem großen Weltobjekt zu sammeln und es dann weiterzugeben. Stattdessen wird empfohlen, den Typ der Funktion, mit der Sie sich befassen, genau anzugeben. Hier sind einige Beispiele:

BAD:
   f :: (World, Int) -> World

Good:
   f :: (Int,Int,Int,Int) -> World

Das schlechte Beispiel versucht, vorhandene Objekte zu modifizieren, aber das gute Beispiel versucht, aus dem Zustandsraum, den Ihr Spiel hat, eine Welt zu erschaffen. Grundsätzlich müssen Sie zum Erstellen einer Welt alle Daten kennen, die zum Auswählen der richtigen Welt erforderlich sind.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.