Moi, un programmeur impératif Java, je voudrais comprendre comment générer une version simple de Space Invaders basée sur les principes de conception de la programmation fonctionnelle (en particulier la transparence référentielle). Cependant, chaque fois que j'essaie de penser à un design, je me perds dans le bourbier de la mutabilité extrême, la même mutabilité qui est rejetée par les puristes de la programmation fonctionnelle.
Pour tenter d'apprendre la programmation fonctionnelle, j'ai décidé d'essayer de créer un jeu interactif 2D très simple, Space Invader (notez le manque de pluriel), à Scala en utilisant le LWJGL . Voici les conditions requises pour le jeu de base:
Navire utilisateur en bas de l'écran déplacé vers la gauche et la droite par les touches "A" et "D" respectivement
Balle de navire de l'utilisateur tirée directement activée par la barre d'espace avec une pause minimale entre les tirs de 0,5 seconde
Balle de vaisseau extraterrestre tirée directement activée par un temps aléatoire de 0,5 à 1,5 seconde entre les tirs
Les éléments intentionnellement exclus du jeu original sont les extraterrestres WxH, les barrières de défense dégradables x3, le vaisseau soucoupe à grande vitesse en haut de l'écran.
Bon, passons maintenant au domaine de problème réel. Pour moi, toutes les parties déterministes sont évidentes. Ce sont les parties non déterministes qui semblent bloquer ma capacité à réfléchir à l'approche. Les parties déterministes sont la trajectoire de la balle une fois qu'elles existent, le mouvement continu de l'étranger et l'explosion due à un coup sur l'un (ou les deux) du navire du joueur ou de l'étranger. Les parties non déterministes (pour moi) gèrent le flux d'entrée utilisateur, gèrent l'extraction d'une valeur aléatoire pour déterminer les tirs de balles extraterrestres et gèrent la sortie (graphique et sonore).
Je peux faire (et j'ai fait) beaucoup de ce type de développement de jeux au fil des ans. Cependant, tout cela provenait du paradigme impératif. Et LWJGL fournit même une version Java très simple des envahisseurs spatiaux (dont j'ai commencé à passer à Scala en utilisant Scala comme Java sans point-virgule).
Voici quelques liens qui parlent de ce domaine dont aucun ne semble avoir traité directement les idées d'une manière qu'une personne issue de la programmation Java / impérative comprendrait:
Il semble qu'il y ait quelques idées dans les jeux Clojure / Lisp et Haskell (avec source). Malheureusement, je ne suis pas en mesure de lire / interpréter le code en modèles mentaux qui ont un sens pour mon cerveau impératif Java simple d'esprit.
Je suis tellement excité par les possibilités offertes par FP, je peux juste goûter aux capacités d'évolutivité multithread. Je me sens comme si j'étais capable de comprendre comment quelque chose d'aussi simple que le modèle temps + événement + aléatoire pour Space Invader peut être implémenté, séparant les parties déterministes et non déterministes dans un système correctement conçu sans qu'il se transforme en ce qui ressemble à une théorie mathématique avancée ; c'est à dire Yampa, je serais prêt. Si l'apprentissage du niveau de théorie que Yampa semble exiger pour réussir à générer des jeux simples est nécessaire, la surcharge d'acquisition de toute la formation et du cadre conceptuel nécessaires dépassera largement ma compréhension des avantages de la PF (au moins pour cette expérience d'apprentissage trop simplifiée). ).
Tout retour d'information, modèles proposés, méthodes suggérées pour aborder le domaine du problème (plus spécifiques que les généralités couvertes par James Hague) seraient grandement appréciés.
la source
Réponses:
Une implémentation idiomatique Scala / LWJGL de Space Invaders ne ressemblerait pas autant à une implémentation Haskell / OpenGL. Écrire une implémentation Haskell pourrait être un meilleur exercice à mon avis. Mais si vous voulez vous en tenir à Scala, voici quelques idées pour l'écrire dans un style fonctionnel.
Essayez d'utiliser uniquement des objets immuables. Vous pourriez avoir un
Game
objet qui contient unPlayer
, unSet[Invader]
(assurez-vous de l'utiliserimmutable.Set
), etc. DonnezPlayer
unupdate(state: Game): Player
(cela pourrait aussi prendredepressedKeys: Set[Int]
, etc.) et donnez aux autres classes des méthodes similaires.Pour le hasard,
scala.util.Random
n'est pas immuable comme HaskellSystem.Random
, mais vous pouvez créer votre propre générateur immuable. Celui-ci est inefficace mais il démontre l'idée.Pour la saisie et le rendu au clavier / souris, il n'y a aucun moyen d'appeler des fonctions impures. Ils sont également impurs dans Haskell, ils sont simplement encapsulés dans
IO
etc. afin que vos objets de fonction réels soient techniquement purs (ils ne lisent ni n'écrivent eux-mêmes, ils décrivent les routines qui le font, et le système d'exécution exécute ces routines) .Ne mettez simplement pas de code d'E / S dans vos objets immuables comme
Game
,Player
etInvader
. Vous pouvez donnerPlayer
unerender
méthode, mais elle devrait ressembler àMalheureusement, cela ne correspond pas bien à LWJGL car il est basé sur l'état, mais vous pouvez créer vos propres abstractions par-dessus. Vous pourriez avoir une
ImmutableCanvas
classe qui contient un AWTCanvas
, et sesblit
(et d'autres méthodes) pourraient cloner le sous-jacentCanvas
, le passer àDisplay.setParent
, puis effectuer le rendu et renvoyer le nouveauCanvas
(dans votre wrapper immuable).Mise à jour : voici du code Java montrant comment j'y arriverais. (J'aurais écrit presque le même code dans Scala, sauf qu'un ensemble immuable est intégré et que quelques boucles pour chaque peuvent être remplacées par des cartes ou des plis.) J'ai fait un joueur qui se déplace et tire des balles, mais je n'a pas ajouté d'ennemis puisque le code devenait déjà long. J'ai fait à peu près tout ce qui est copie sur écriture - je pense que c'est le concept le plus important.
la source
args
si le code ignore les arguments. Désolé pour la confusion inutile.GameState
copies seraient aussi coûteuses, même si plusieurs sont faites à chaque tick, car elles sont ~ 32 octets chacune. Mais la copie desImmutableSet
s pourrait coûter cher si de nombreuses balles sont vivantes en même temps. Nous pourrions remplacerImmutableSet
par une structure arborescente commescala.collection.immutable.TreeSet
pour atténuer le problème.ImmutableImage
c'est encore pire, car il copie un grand raster lorsqu'il est modifié. Il y a certaines choses que nous pourrions faire pour atténuer ce problème aussi, mais je pense qu'il serait plus pratique d'écrire simplement du code de rendu dans un style impératif (même les programmeurs Haskell le font normalement).Eh bien, vous entravez vos efforts en utilisant LWJGL - rien contre, mais cela imposera des idiomes non fonctionnels.
Votre recherche est cependant conforme à ce que je recommanderais. Les «événements» sont bien pris en charge dans la programmation fonctionnelle grâce à des concepts tels que la programmation réactive fonctionnelle ou la programmation de flux de données. Vous pouvez essayer Reactive , une bibliothèque FRP pour Scala, pour voir si elle peut contenir vos effets secondaires.
Prenez également une page de Haskell: utilisez des monades pour encapsuler / isoler les effets secondaires. Voir les monades d'état et d'E / S.
la source
Oui, les IO sont des effets secondaires non déterministes et «tout sur». Ce n'est pas un problème dans un langage fonctionnel non pur comme Scala.
Vous pouvez traiter la sortie d'un générateur de nombres pseudo-aléatoires comme une séquence infinie (
Seq
dans Scala)....
Où, en particulier, voyez-vous le besoin de mutabilité? Si je peux m'y attendre, vous pourriez penser que vos sprites ont une position dans l'espace qui varie avec le temps. Vous pouvez trouver utile de penser aux "fermetures à glissière" dans un tel contexte: http://scienceblogs.com/goodmath/2010/01/zippers_making_functional_upda.php
la source