Programmation fonctionnelle et aventures textuelles

14

Il s'agit principalement d'une question théorique sur la PF, mais je vais prendre des aventures textuelles (comme la vieille école de Zork) pour illustrer mon propos. J'aimerais connaître votre opinion sur la façon dont vous modéliseriez une simulation avec état avec FP.

Les aventures textuelles semblent vraiment appeler la POO. Par exemple, toutes les "salles" sont des instances d'une Roomclasse, vous pouvez avoir une Itemclasse de base et des interfaces comme Item<Pickable>pour les choses que vous pouvez transporter et ainsi de suite.

La modélisation des mondes dans FP fonctionne différemment, surtout si vous voulez imposer l'immuabilité dans un monde qui doit muter au fur et à mesure que le jeu progresse (les objets sont déplacés, les ennemis sont vaincus, le score augmente, le joueur change d'emplacement). J'imagine un seul grand objet Worldqui a tout: quelles sont les pièces que vous pouvez explorer, comment elles sont liées, ce que le joueur porte, quels leviers ont été déclenchés.

Je pense qu'une approche pure serait de passer ce gros objet à n'importe quelle fonction et de le faire retourner par eux (éventuellement modifié). Par exemple, j'ai une moveToRoomfonction qui l'obtient Worldet la renvoie avec une World.player.locationmodification dans la nouvelle pièce, World.rooms[new_room].visited = Trueetc.

Même si c'est la manière la plus "correcte", cela semble être une pureté pour le plaisir. Selon le langage de programmation, le passage de cet Worldobjet potentiellement très gros dans les deux sens peut être coûteux. En outre, chaque fonction peut avoir besoin d'avoir accès à n'importe quel Worldobjet. Par exemple, une pièce peut être accessible ou non selon un levier déclenché dans une autre pièce car elle peut être inondée, mais si le joueur porte un gilet de sauvetage, il peut y entrer quand même. Un monstre peut être agressif ou non selon que le joueur a tué son cousin dans une autre pièce. Cela signifie que la roomCanBeEnteredfonction a besoin d'accéder World.player.invetoryet World.rooms, a describeMonsterbesoin d'accéder World.monsterset ainsi de suite ( en gros, vous devezfaire passer toute la charge). Cela me semble vraiment appeler une variable globale, même si c'est tout sauf un bon style de programmation surtout en FP.

comment résoudrais-tu ce problème?

pistache
la source
4
"Selon le langage de programmation, le passage de cet objet World potentiellement très gros peut être coûteux." Il sera probablement adopté par référence. "De plus, chaque fonction peut avoir besoin d'avoir accès à n'importe quel objet World." Je trouve difficile de croire que chaque fonction a besoin d'accéder à tout l' état du jeu.
Doval
2
Je pense que la recherche de Chris Marten serait intéressante, elle vise à montrer comment rendre agréable la fiction interactive dans des langages déclaratifs. github.com/chrisamaphone/interactive-lp
Daniel Gratzer
2
Vous voudrez peut-être jeter un œil à ce blog décrivant l'approche de l'auteur pour programmer un tel jeu de manière fonctionnelle. Ce post en particulier est tout à fait pertinent.
gallais
3
Je dois me demander si cette question a influencé la décision ultérieure d'EricLippert d'écrire une série d'articles sur l'implémentation (en partie) d'une machine Z dans Ocaml ...?
Jules
1
@Jules Un lien vers le début de cette série, pour les personnes intéressées: ericlippert.com/2016/02/01/west-of-house
KChaloux

Réponses:

4

Gardez à l'esprit que les langages fonctionnels utilisent des structures de données et des fonctions séparées au lieu d'objets. Par exemple, vous auriez à la place un ensemble de pièces et une liste d'articles d'inventaire en tant que monde.

Idéalement, vous limiteriez également la quantité de données que vous donnez aux fonctions à ce dont elles ont réellement besoin autant que possible au lieu de traverser le monde entier (par exemple, vous extrayez une seule pièce pertinente de votre monde; bien sûr, des mondes complètement interdépendants peuvent être difficiles à séparé). Le résultat serait réintégré dans la structure de données mondiale, créant un nouvel état. Vous ne pouvez pas modéliser l'état sans utiliser l'état; comme vous le dites, certaines choses nécessitent intrinsèquement une mutation.

La plupart des langages fonctionnels pratiques fournissent un moyen de réaliser la mutation soit directement (par exemple la monade ST dans Haskell ou les transitoires dans Clojure), soit de la simuler efficacement (souvent en réutilisant des parties inchangées de la structure de données (les structures de données par défaut de Clojure sont un bon exemple ici) ). Dans tous les cas, la pureté est maintenue.

Étant donné que la quantité d'état à muter semble limitée, je ne m'inquiéterais pas trop des problèmes de performances et adopterais l'approche fonctionnelle naïve (peut-être déjà optimisée).

Une autre option que j'ai vue serait de renvoyer uniquement des instructions pour changer une partie du monde de vos fonctions individuelles, puis de mettre à jour votre monde en fonction de celles-ci. Une série d'articles de blog décrivant cela est disponible sur http://prog21.dadgum.com/23.html .

Ces deux réponses traitent plus de la façon d'organiser les changements que de ne pas passer votre monde entier aux fonctions, car un parfaitement interdépendant ne peut pas être segmenté par définition - mais essayez de le faire du mieux que vous pouvez dans votre cas, ce qui n'est pas seulement fonctionnel, mais aussi de bonnes pratiques.

hyperfekt
la source
0

Moi-même, j'examinerais certainement la capacité de la langue en question à accéder à une certaine forme de base de données. La plupart des événements qui changent simultanément l'état du monde seraient simplement enregistrés sur le disque et n'affecteraient pas le joueur actuel dans la salle actuelle (en dehors de circonstances spéciales telles que des explosions ou dans un MMO, des interrupteurs qui ouvrent des portes). à distance, cris des autres joueurs, etc.).

En tant que tel, le client actuel n'a vraiment besoin que de connaître l' Roomobjet et les choses qui l'affectent directement. noticableEventsOutsideRoompourrait alors simplement être une sous-classe de personnes Roomaffectées par les modifications récentes de la base de données, et votre objet de jeu est devenu beaucoup plus petit.

Ayelis
la source
Je comprends cette approche ne fait pas beaucoup pour pathfinding ou le déclenchement d' événements locaux (tels que l' agro sur les foules à proximité), mais j'ai connu aux bases de données d'abus dans le passé ... Je probablement juste envoyer un appel à update mobs set agro=1 where distance<5et être fait avec elle. Ce n'est peut-être pas la meilleure pratique, mais cela convient à mes besoins. Quant à la recherche de chemin via une base de données, on pourrait toujours utiliser l'algorithme de chemin le plus court de Dijkstra ...
Ayelis
0

La vraie solution n'est pas de tout rassembler dans un grand objet du monde, puis de le faire circuler. Au lieu de cela, il est recommandé de spécifier avec précision le type de la fonction avec laquelle vous traitez. Voici quelques exemples:

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

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

Le mauvais exemple essaie de modifier un objet existant, mais le bon exemple essaie de créer un monde à partir de l'espace d'état de votre jeu. Fondamentalement, pour créer un monde, vous devez connaître toutes les données nécessaires pour sélectionner le monde approprié.

tp1
la source