:tocdepth: 1 .. include:: common.rst.inc Projet : jeu Snake ================== Principe -------- Objectif ++++++++ L'objectif de votre projet est de créer un clone du jeu *Snake* (comme `celui-ci`__). Vous le rendrez sous la forme d'un **dépot GIT** dont vous fournirez l'URL. __ http://patorjk.com/games/snake/ .. warning:: Il existe de nombreux tutoriels en ligne pour créer un jeu de Snake en Javascript. Il n'est pas interdit de vous en inspirer, mais on attend de vous que vous écriviez votre *propre* code, pas que vous recopiiez passivement le code du tutoriel. Notamment, votre note dépendra largement du respect des recommandations ci-dessous (les tutoriels optent généralement pour des solutions différentes). .. nextslide:: Votre application comportera au minimum deux "écrans" : * un écran d'accueil, présentant le jeu et permettant de choisir parmi différents niveaux (cf. ci-dessous), * un écran de jeu, où se déroule le jeu à proprement parler, en l'affichant dans un canvas 2D, et en affichant le score du joueur. Le passage d'un écran à l'autre doit se faire sans changer d'URL (vous devez donc *modifier* la page par manipulation du DOM, et non en chargeant une nouvelle page HTML). Représentation interne ++++++++++++++++++++++ L'état du monde est représenté dans un tableau JS à deux dimensions. Chaque cellule du tableau représente une "case" du monde, et indique (par sa valeur) ce ce que contient cette case (mur, serpent, nourriture...). Il vous est recommandé de définir des constantes correspondant à chaque type d'objet, pour améliorer la lisibilité du programme. .. nextslide:: .. figure:: _static/snake-grid.* Exemple de grille. .. code-block:: javascript // La représentation qu'aurait cette grille en Javascript let WORLD = [ [EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY], [EMPTY, EMPTY, EMPTY, FOOD, EMPTY, EMPTY], [EMPTY, SNAKE, EMPTY, EMPTY, EMPTY, EMPTY], [EMPTY, SNAKE, EMPTY, EMPTY, EMPTY, EMPTY], [EMPTY, SNAKE, SNAKE, EMPTY, EMPTY, EMPTY], [EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY], ]; .. nextslide:: Par ailleurs, l'état du serpent est également représenté par un tableau, dont chaque cellule contient les coordonnées d'une case du serpent. Le début de ce tableau correspond à la queue du serpent, et la fin du tableau correspond à sa tête. Lorsque le serpent se déplace, on ajoutera la nouvelle position de sa tête à la fin (avec la méthode `push`_) et on retirera l'ancienne position de sa queue (avec la méthode `shift`_). .. code-block:: javascript // La représentation qu'aurait le serpent dans la figure ci-dessus. let SNAKE_BODY = [[4,2], [4,1], [3,1], [2,1]]; L'information sur le serpent est donc stockée de manière *redondante* dans ces deux tableaux. Cette redondance rendra la gestion du jeu plus simple par la suite. Contrôles +++++++++ L'événement ``keydown`` est produit chaque fois que l'utilisateur enfonce une touche. L'objet événement passé en paramètre du *listener* possède un attribut ``key``, qui représente la touche enfoncée. Lorsque la touche correspond à un caractère imprimable, ``key`` contient ce caractère. Sinon, il contient le nom de la touche, comme décrit dans `ces tableaux`__. En particulier, les flèches sont représentées respectivement par les chaînes ``"ArrowDown"``, ``"ArrowLeft"``, ``"ArrowRight"`` et ``"ArrowUp"``. __ https://www.w3.org/TR/uievents-key/#named-key-attribute-values Chaque fois qu'une touche pertinente pour le jeu est enfoncée, vous mémoriserez dans une variable la valeur de l'attribut ``key``. Cette valeur sera utilisée dans la fonction ``step`` (cf. ci-dessous). Déroulement d'une partie ++++++++++++++++++++++++ Toute la mécanique du jeu sera gérée dans une fonction nommée ``step``, qui sera appelée à intervalle régulier, grâce à la fonction `setInterval`_. Dans cette fonction, vous devrez effectuer les étapes suivantes. #. Vérifier si l'utilisateur a enfoncé une touche, et modifier la direction du serpent en conséquence (si cela est compatible avec sa direction actuelle). #. Calculer la nouvelle position de la tête du serpent en fonction de sa direction. .. nextslide:: #. Vérifier si la tête du serpent rencontre de la nourriture, un mur, ou un morceau de son corps. * Dans le premier cas, le score augmente, et une autre nourriture est ajoutée dans une case vide aléatoire. * Dans les autres cas, la partie se termine. #. Mettre à jour le tableau ``SNAKE_BODY`` en faisant avancer le serpent ; s'il a mangé de la nourriture, son corps doit s'allonger (ce qui revient à ne pas réduire sa queue). Mettre également à jour le tableau ``WORLD`` en conséquence. #. Effacer intégralement le canvas, et re-dessiner l'état de WORLD. (On pourrait envisager de ne redessiner que les parties qui ont changées, mais cette méthode est plus simple et plus évolutive). .. _push: https://devdocs.io/javascript/global_objects/array/push .. _shift: https://devdocs.io/javascript/global_objects/array/shift .. _setInterval: https://devdocs.io/dom/windoworworkerglobalscope/setinterval Gestion des niveaux +++++++++++++++++++ Chaque niveau est décrit par un fichier JSON ayant la structure suivante: .. code-block:: JSON { "dimensions": [80, 40], "delay": 200, "walls": [ [5,5], [5,6], [5,7], [5,8], [70, 35], [71, 35], [72, 35] ], "food": [ [10,10] ], "snake": [ [60,30], [60,29], [60,28] ] } .. nextslide:: où * ``dimensions`` donne la taille de la grille dans laquelle évolue le serpent, * ``delay`` donne le délais (en ms) entre deux appels à la fonction ``step``, * ``walls`` donne la position des cases de mur, * ``food`` donne la position initiale de la nourriture * ``snake`` donne la position initiale du serpent La page de garde doit donner le choix entre plusieurs niveaux ; lorsque le joueur en choisit un, le fichier JSON correspondant est chargé, et les variables ``WORLD`` et ``SNAKE_BODY`` sont initialisées conformément. Extensions ++++++++++ Le cahier des charges ci-dessus est minimaliste. À vous de faire preuve d'originalité en l'étendant. Par exemple : * Complexification du jeu (en étendant le format JSON de description des niveaux) : - faire accélérer le serpent progressivement (selon les niveaux); - faire disparaître la nourriture automatiquement au bout d'un certain temps (pour la faire réapparaître ailleurs) ; - ajouter d'autres types de terrain (par exemple, la glace, qui empêche de changer de direction) ; - offrir plusieurs types de nourriture, certains offrant des capacités spéciales (diminuer la taille du serpent au lieu de l'augmenter, creuser sous terre...) ; - offrir un mode multi-joueur (sur le même poste)... .. nextslide:: * Améliorations esthétiques : - intégrer des sons ; - utiliser des images bitmaps plutôt que des carrés monochromes ; - utiliser un canvas WebGL pour représenter le jeu...