Avant de t'attaquer à ce défi, on te conseille de compléter les défis suivants:

  1. En 2020 je serai...
  2. Ça va bien aller
  3. Péter ta balloune
  4. Bulles de savant
  5. La nuit étoilée
  6. Les étu-danses

Première chose avant de commencer à travailler sur n'importe quel projet p5.js: connecte-toi à ton compte en cliquant sur "Log in".

L'objectif de ce défi, c'est de créer un jeu en utilisant les classes. Tu auras besoin de comprendre un minimum à quoi servent les classes avant de commencer, mais tu n'as pas besoin d'une connaissance parfaite, comme tu apprendras à mieux les utiliser à travers ce challenge.

On va coder le fameux jeu de Snake, un jeu d'arcade qui était très populaire dans le début dans années '80.

Pour ceux et celles qui ne connaissent pas le jeu: Le joueur doit contrôler un serpent, qui doit se déplacer dans un cadre pour manger des pommes. Lorsqu'il mange une pomme, il devient plus long, mais gagne aussi des points. Lorsque le serpent fonce dans le mur ou dans soi-même, il meurt. Le but est d'obtenir un serpent le plus long possible.

Tu peux essayer la version que propose Google. Ce jeu est bien sûr plus rafiné que ce qu'on compte créer, mais on pourra s'inspirer de leur jeu pour créer le notre.

Jeu de Snake Google

Notre jeu va être un peu plus simple que celui de Google. On va se concentrer sur les éléments principales du jeu d'abord, c'est-à-dire le serpent et les pommes. On ne va pas se concentrer sur les graphismes au début, comme on sait déjà faire ça!

On a mentionné plus haut qu'on allait utiliser des classes. Peux tu deviner quel(s) objet(s) on va mettre dans des classes?

On a deux objets principaux dans notre jeu, comme mentionné plus tôt: Le serpent et les pommes. Pourtant un des deux objets est bien plus simple que l'autre, et ne requiert pas vraiment une classe.

Bien vu, on va créer une seule classe dans notre code, et cette classe répresentera le serpent!

Pour alléger le code, on va placer notre classe dans un fichier séparé. Tu peux aller créer ce nouveau fichier. Appelle-le quelque chose de répresentatif, comme par exemple snake.js ("snake" veut dire "serpent" en anglais).

nouveau Fichier

Finalement il va falloir importer ce fichier dans ton code. Parce que pour le moment, le code ne sait pas qu'il doit utiliser le fichier snake.js. Pour ce faire, va dans le fichier index.html. Ce fichier est le fichier qui affiche ton code, il est très important!

Pour le moment il ressemble à ceci:


              <!DOCTYPE html>
              <html lang="en">
                <head>
                  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.10.2/p5.js"></script>
                  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.10.2/addons/p5.sound.min.js"></script>
                  <link rel="stylesheet" type="text/css" href="style.css">
                  <meta charset="utf-8" />

                </head>
                <body>
                  <script src="sketch.js"></script>
                </body>
              </html>
            

Tu vois la 3e ligne du bas? Celle-ci:

<script src="sketch.js"></script>
Tu peux la copier et la remettre pile en dessous, en changeant le src="" pour mettre le bon nom de fichier.

Ton code doit ressembler à ceci du coup:


                  <!DOCTYPE html>
                  <html lang="en">
                    <head>
                      <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.10.2/p5.js"></script>
                      <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.10.2/addons/p5.sound.min.js"></script>
                      <link rel="stylesheet" type="text/css" href="style.css">
                      <meta charset="utf-8" />

                    </head>
                    <body>
                      <script src="sketch.js"></script>
                      <script src="snake.js"></script>
                    </body>
                  </html>
                

Super! On est prêts à coder maintenant!

On va maintenant procéder à la création de notre classe! Te souviens tu comment créer une classe?

La base de la classe ressemble à ceci:


              class NomDeLaClasse {
                // Code de la class
              }
            

Toutes les classes ont une méthode (Une fonction dans une classe) en commun. Te souviens tu de laquelle il s'agit?

C'est la méthode contructor()


              constructor() {
                // Code du constructor
              }
            

Super! Place donc maintenant la méthode constructor() dans ta classe.


              class Snake {
                constructor() {
                  // Code du constructor
                }
                // Restant du code de la classe
              }
            

Dans sketch.js, on veut maintenant créer une instance de cette classe (Une instance est un objet qui suit les règles de la classe. Tu peux penser à la classe comme étant une classe sociale, et les instances comme étant les personnes appartenant à cette classe sociale).

Pour créer une instance d'une classe, on utilise le mot new NomDeLaClasse(). Mais on ne veut créer qu'une seule instance, comme on n'aura qu'un seul serpent. Tu dois donc la définir dans la fonction (Ici c'est bien une fonction comme on n'est pas à l'intérieur d'une classe!) setup().

Tu dois définir la variable à l'extérieur de la fonction setup(), mais l'assigner à l'intérieur de la fonction setup()!


              // Définir la variable

              function setup() {
                // Assigner la variable
                createCanvas(400, 400);
              }
            


              // Définir la variable
              let snake;

              function setup() {
                // Assigner la variable
                snake = new Snake();  

                createCanvas(400, 400);
              }
            

Une caractéristique importante du serpent est qu'on puisse le voir. Sans cela, le jeu n'a pas réellement de sens.

On doit donc créer une première méthode dans la classe Snake, qui lui permettra d'être vu.

En général, on essaye de choisir des noms de variables, de fonctions, de classes, de méthodes, ... qui ont un sens, et en général qui ont un sens en anglais, comme c'est la langue universelle des développeurs. Pour une méthode qui montre le serpent, on peut donc choisir show().

Essaie de définir la méthode show() et fait en sorte qu'elle dessine un simple rectangle la où tu veux de la taille que tu veux. Ensuite, appelle cette méthode dans sketch.js

Pour définir une méthode, tu dois insérer ce code dans ta classe


              nomDeLaMéthode() {
                // Code de la méthode
              }
            

Et pour ensuite appeller la méthode, tu peux simplement faire


              Snake.nomDeLaMéthode();
            

Dans ma méthode, je dessine un carré en haut à gauche de 20 pixels par 20 pixels


              class Snake {
                constructor() {

                }
                show() {
                  rect(0, 0, 20, 20);
                }
              }
            

Ensuite on appelle la fonction dans la fonction draw()


              let snake;

              function setup() {
                snake = new Snake();
                createCanvas(400, 400);
              }

              function draw() {
                background(220);
                snake.show();
              }
            

On peut montrer notre petit carré maintenant, mais on ne sait toujours pas le bouger. On doit détecter lorsqu'on appuie sur une touche. On peut faire cela en utilisant la fonction keyPressed()! Définis cette fonction dans ton code, après les fonctions setup() et draw()


              function setup() {
                // ...
              }
              function draw() {
                // ...
              }
              function keyPressed() {
                // ...
              }
            

Mais cette nouvelle function est inutile si on ne peut pas déterminer quelle touche est pressée. Heureusement pour nous, on peut utiliser le paramètre keyCode à l'intérieur de la fonction. Essaie de logger ce paramètre à l'intérieur de la fonction pour voir ce à quoi il ressemble!

Pour logger une variable dans la console, tu peux utiliser console.log(nomDeVariable).


              function keyPressed() {
                console.log(keyCode);
              }
            

Détermine les keyCode des 4 flèches et indique les dans les cases suivants.


Ces nombres qui apparaissent dans la console ne nous apprennent pas énormement au final. On aimerait bien logger "Flèche Haut" quand on appuie sur la flèche en haut par exemple.

Pour ce faire, on va utiliser une condition. Une condition est une bloc comme le suivant:


          if( condition ) {
            // Code à exécuter si la condition est vrai
          }
        

La condition est quelque chose qui ressemble à

variable === valeur
Dans ce cas ci, on veut vérifier si le keyCode vaut un certain nombre. Si c'est le cas, on veut logger "Flèche Haut".

À toi de complèter les ....


              function keyPressed() {
                // Flèche Haut
                if(keyCode === 38) {
                  console.log("Flèche Haut");
                }
                // Flèche Droite
                // ...
                // Flèche Bas
                // ...
                // Flèche Droite
                // ...
              }
            

Bon c'est bien gentil que la console nous dise quelle flèche on touche, mais notre carré ne bouge toujours pas... Il nous faut transmettre l'information à la class Snake, et après il faudra créer une méthode pour bouger.

Commençons par transmettre l'information à la classe. Ce n'est en réalité aps super compliqué! On peut créer des variables dans notre classe, et les appeller en utilisant snake.nomDeVariable.

Plutôt que de logger "Flèche Haut", etc.. lorsqu'on appuie sur une flèche, on peut assigner la valeur "Haut" à la variable snake.direction. Ensuite, dans notre méthode show(), on peut ajouter un console.log(this.direction).

Dans ton sketch.js, tu veux changer la direction à chaque fois qu'une touche est pressée.


              function keyPressed() {
                if(keyCode === 38) {
                  snake.direction = "Haut"
                }
                // Autres directions
                // ...
              }
            

Dans ton snake.js, tu veux logger la direction à chaque fois que ton serpent s'affiche.


              class Snake() {
                // constructor
                display() {
                  console.log(this.direction)
                  // ....
                }
              }
            

Bon... On a maintenant réussi à faire logger l'instance dans quelle direction elle veut aller. On doit maintenant définir une méthode qui fait bouger l'instance.

Cette nouvelle méthode fera bouger le serpent, donc on peut l'appeller move(). Son but est de faire changer la position X et Y du serpent. Donc pour commencer, on doit placer ces positions dans des variables propres à l'instance. Pour ce faire, on doit les définir dans le constructor()

N'oublie pas que lorsque tu assignes un variable dans le constructor, tu dois utiliser le mot clé this..

On peut déjà assigner nos variables avec des valeurs, étant les valeurs initiales.

              constructor() {
                this.positionX = 0;
                this.positionY = 0;
              }
            

Dans notre méthode show(), on va plutôt utiliser ces variables. Il te suffit de changer les 2 premiers paramètres de ta fonction rect()

Finalement, on veut créer une nouvelle méthode, la méthode move(). Cette méthode doit traîter 4 cas dépendant de quelle flèche a été pressée en dernier. On a déjà stocké cette valeur dans une variable, la variable this.direction.

Pour bouger vers le haut, on peut simplement faire le suivant:


          move() {
            // Bouger vers le haut
            if(this.direction === "Haut") {
              this.positionY -= 20;
            }
            // Bouger vers la droite
            // ...
            // Bouger vers le bas
            // ...
            // Bouger vers la gauche
            // ...
          }
        

Tu peux compléter les autres parties ;-)

Il ne nous reste plus qu'une chose à faire pour que le serpent sache bouger. Sais-tu ce qu'on a oublié?

On n'a jamais appellé notre nouvelle méthode.

Dans la fonction draw(), tu peux appeller la méthode sur ton instance


              function draw() {
                // ...
                snake.move();
              }
            

Tu trouves peut-être que le serpent bouge trop rapidement. Pour le faire bouger plus lentement, tu peux adapter le frameRate() dans la fonction setup()

Notre serpent doit également être capable de grandir lorsqu'il mange une pomme. On n'a pas encore codé les pommes, et on ne va pas encore le faire. En effet, on va simplement dire que pour grandir, il faut appuyer sur la touche espace. Retrouve le keyCode de la touche espace comme tu as retrouvé les autres keyCode plus tôt. Lorsqu'on appuie sur la touche espace, on aimerait effectuer la méthode grow() du serpent, qui le fera grandir. Il nous faut coder cette méthode.

D'abord, ajoute une condition dans la fonction keyPressed() pour vérifier si la touche espace est pressée, et si c'est le cas effectuer la méthode grow() du serpent (Même si elle n'existe pas encore).


              function keyPressed() {
                // ...
                if(keyCode === 32) {
                  snake.grow();
                }
              }
            

Si tu essaies d'appuyer sur la touche espace, tu devrais voir une erreur comme la méthode n'existe pas! Allons donc la créer. Pour un début, fait simplement en sorte que ta méthode logge un message.


              grow() {
                console.log("Je grandis!");
              }
            

Sois sûr que ton code fonctionne bien. Si tu vois le message, c'est que tu as bien créé ta méthode.

On va devoir changer quelques choses fondamentales dans notre classe. En effet, on doit maintenant se souvenir de toute une liste de blocs qui composent le serpent. Chaque bloc est composé d'une positionX et d'une positionY.


          constructor() {
            // La liste est vide de base
            this.snakeBlocks = [];
            // On ajoute le premier bloc
            this.snakeBlocks[0] = {positionX: 0, positionY: 0};
            // ...
          }
        

Si on essaie de faire marcher le code maintenant, rien n'a changé. On doit bien sûr utiliser ces nouvelles variables quelque part! Commençons par changer la méthode show(). On ne veut pas juste montrer un rectangle, mais tous les rectangles!


          show() {
            for(let i = 0; i < this.snakeBlocks.length; i++) {
              rect(this.snakeBlocks[i].positionX, this.snakeBlocks[i].positionY, 20, 20);
            }
          }
        

Le for(...) {...} est une boucle, et on boucle sur tous les blocs du serpent. Le i va en fait de 0 jusqu'au nombre de blocs -1, et on utilise le i pour repérer de quel bloc on parle à chaque itération.

Maintenant le code ne fonctionne plus! Hmm... Qu'est ce qu'on doit encore changer pour que cela fonctionne?

Oui! On doit changer la méthode move(). Pour le moment, elle ne fait que changer les variables positionX et positionY, mais on n'utilise plus ces variables directement. Souviens-toi qu'on avait dit que pour simuler le mouvement on va toujours effacer le dernier bloc et en re-dessiner un autre au début!

Pour effacer le dernier code, on doit simplement l'enlever de notre liste. Pour enlever le dernier élément d'une liste, on utilise la méthode liste.pop().


          move() {
            // Code pour ajuster positionX et positionY
            this.snakeBlocks.pop();
          }
        

Bon ok, c'est encore pire qu'avant, maintenant le seul bloc qu'on avait (Qui ne bougeait pas), disparaît immédiatement... Aie... Mais c'est parce qu'on ne le re-dessine pas!

Pour ajouter un nouvel élément au début d'une liste, on utilise la méthode liste.unshift(élément). Souviens-toi à quoi ressemblait le premier élément et essaie de re-créer cet élément avec les nouvelles variables.


              move() {
                // Code pour ajuster positionX et positionY
                this.snakeBlocks.pop();
                this.snakeBlocks.unshift({positionX: this.positionX, positionY: this.positionY});
              }
            

C'est super! Notre serpent sait de nouveau "bouger"! Mais la touche espace ne fait toujours pas réellement grandir le serpent. En fait, pour le faire grandir, on va simplement copier le dernier élément de la liste. Ainsi, le serpent aura une longueur plus longue et ce sera bouclé. A-tu une idée de comment faire?

Tu peux assigner this.snakeBlocks[this.snakeBlocks.length] (Il n'existe pas encore!) à la même valeur que this.snakeBlocks[this.snakeBlocks.length-1] (Dernier élément de la liste)


              grow() {
                this.snakeBlocks[this.snakeBlocks.length] = this.snakeBlocks[this.snakeBlocks.length-1];
              }
            

SUPER! On peut maintenant faire grandir notre serpent à l'aide de la touche espace! Bien joué!

Normalement tu as maintenant un "serpent" qui peut bouger dans toutes les directions et grandir à l'aide de la touche espace. On va maintenant essayer de faire perdre notre serpent s'il fonce dans le mur.

Commencons par créer une nouvelle méthode qui vérifie si on a perdu. Une méthode peut retourner une valeur. Par exemple, on pourrait créer une méthode qui s'appelle "Fait-il beau aujourd'hui?" et qui nous répond oui ou non dépendant de la journée.

Notre méthode doit retourner Oui si on a perdu, et Non sinon. En langage ordinateur, on dit pas "Oui" ou "Non", mais true (Pour "Oui") et false (Pour "Non").

Pour faire en sorte que notre méthode retourne une valeur, on utilise le mot clé return. On a par exemple notre méthode faitIlBeau():


          faitIlBeau() {
            if(ilFaitBeau) {
              return true;
            } else {
              return false;
            }
          }
        

Essaie de coder la méthode dont on a besoin pour vérifier si on a perdu. Appelle la méthode lost() (Anglais pour "Perdu") et fait simplement en sorte qu'elle retourne toujours false. Comme cela, pour un début du moins, on n'a jamais perdu.


              class Snake() {
                lost() {
                  return false;
                }
              }
            

Lorsqu'on a perdu, on n'a plus envie de voir le serpent. On préfere voir un message disant qu'on a perdu. Ainsi, tu peux placer le snake.display() de notre sketch.js dans une condition qui vérifie si snake.lost() est "Oui" ou "Non".


              function draw() {
                if(snake.lost() === false) {
                  snake.display();
                }
                // ...
              }
            

Lorsqu'on n'est plus en vie, c'est à dire lorsqu'on a perdu, on aimerait qu'un text apparaisse sur l'écran nous disant qu'on a perdu. Tu peux changer la condition de la remarque précedente en un


          if(...) {
            //...
          } else {
            //...
          }
        

Dans le else {//....}, tu peux placer le code qui doit s'executer lorsque la condition n'est pas vérifiée.

Super. On doit maintenant un peu adapter notre méthode lost()... Commençons par la faire detecter si on est en dehors de l'écran!

Tu te souviens que la position de la tête du serpent est stoquée dans les variables this.positionX et this.positionY? Si la tête n'est pas en dehors de l'écran, alors le restant du serpent ne l'est pas non plus. Donc il nous suffit de vérifier si la tête est en dehors.

Si positionX est plus petit que 0, alors on sait que la tête est à gauche de l'écran, et donc sorti de l'écran. Ceci se code ainsi:


          if(this.positionX < 0) {
            // ....
          }
        

On doit ajouter ce code dans la méthode lost(). Poir ce faire, il suffit de la rajouter dedans. Lorsque la condition est vraie, on veut que snake.lost() soit "Non", donc on veut retourner false


              lost() {
                if(this.positionX < 0) {
                  return false;
                }
                // ...
              }
            

Tu devrais être capable de faire un même raisonement pour vérifier si le serpent est au dessus de l'écran.

Pour vérifier si le serpent est à droite de l'écran, c'est un peu plus compliqué. La longueur totale sur l'axe gauche-droite (L'axe des X) est de 400. Si this.positionX vaut 400, cela veut dire que le premier carré a son coin supérieur droit sur le bord droite de l'écran, donc il est pile en dehors de l'écran. Donc pour vérifier s'il est en dehors à droite, on écrit le code suivant:


          if(this.positionX >= 400) {
            // ....
          }
        

Tu peux toi-même l'ajouter dans la méthode lost(). Tu peux aussi faire la vérification lorsque le serpent est en dessous de l'écran.

Essaie si ta méthode fonctionne! Normalement, il manque une petite chose... As-tu une idée de ce que c'est?

Ta méthode lost() ne retourne jamais la valeur false. Elle peut ne rien retourner, mais elle ne retourne jamais la valeur false... Pour règler cela, ajouter simplement un return false; tout à la fin de la méthode.


          lost() {
            // Vérification pour "À gauche"
            if(this.positionX < 0) {
              return true;
            }
            // Vérification pour "À droite"
            // Vérification pour "En haut"
            // Vérification pour "En bas"

            return false;
          }
        

Maintenant, si la méthode n'a rien à retourner, elle va retourner false, et ton code devrait marcher!

Pour rendre le jeu plus réel, on va devoir ajouter des pommes pour faire grandir le serpent plutôt que de devoir appuyer sur la touche espace tout le temps.

On va faire en sorte que ce soit la classe du serpent qui dessine notre pomme. Ce sera beaucoup plus facile de cette façon pour vérifier si le serpent à mangé la pomme ou non.

Crée une nouvelle méthode qui s'appelle apple(). Commence par faire en sorte que cette méthode dessine un ractangle rouge quelque part.


          apple() {
            fill("red");
            rect(20, 20, 20, 20);
          }
        

Essaie de reproduire ce code et de le lancer. Pour voir les effets, tu dois pas oublier d'appeller la méthode quelque part. Je te conseille d'appeller la fonction à la fin de ta méthode show(). N'oublie pas que comme tu es dans la classe, tu dois utiliser le mot clé this. pour l'appeller.

Normalement tu vois que ton serpent devient rouge aussi. C'est parce que tu as utilisé un fill("red") dans le code de ta apple(). Avant de dessiner les blocs de ton serpent, tu peux ajouter un fill() avec la couleur de ton choix pour le serpent.

Pour savoir où se trouve la pomme, on doit lui attribuer une variable. Tu peux appeller ces variables this.appleX et this.appleY. Commence par les placer à un endroit fixe sur l'écran.


          constructor() {
            this.appleX = 20;
            this.appleY = 20;
          }
        

Pour vérifier si le serpent a mangé la pomme ou non, il faut vérifier si la tête se trouve au même endroit que la pomme. Si c'est le cas, on aimerait que le serpent grandisse. Comment vérfier si la tête se trouve au même endroit que la pomme?

On utilise les signes && pour vériier si plusieurs conditions sont vérfiées. C'est une sorte de "et".


              if(this.positionX === this.appleX && this.positionY === this.appleY) {
                // ...
              }
            

Lorsque cette condition est vraie, tu peux appeller la méthode this.grow(). Par ailleurs, tu peux appeller cette condition dans le code de apple()!

Donc on a maintenant un jeu où notre serpent bouge dans tous les sens, perd s'il touche le bord et grandis lorsqu'il mange une pomme. Il ne nous manque plus qu'à faire bouger la pomme entre chaque fois qu'on la mange!

On veut la faire apparaître sur les cases avec des coordonnées mulitples de 20. Comme cela, le serpent peut tomber pile dessus! Comment peut-tu produire un nombre aléatoire entre 0 et 380 qui est un mulitple de 20?


          random(20);
        

Ce code va produire un nombre aléatoire entre 1 et 20. Mais il peut très bien être 17.3218... Nous on aimerait des nombres entiers!

Pour prendre le nombre entier le plus proche d'un nombre à virgule, on utilise la function floor(). Par exemple:


              floor(17.2) = 17;
              floor(3.1415) = 3;
            

Il nous faut commencer par trouver un nombre aléatoire entre 1 et 20, et ensuite le multiplier par 20. Ceci va donner un mulitple aléatoire de 20 entre 1 et 400.


              20 * floor(random(20));
            

Lorsqu'on mange la pomme, on aimerait changer sa position. On peut simplement re-assigner les variables this.appleX et this.appleY à un multiple de 20 aléatoire entre 0 et 400


              apple() {
                // Dessiner la pomme
                
                if(this.positionX === this.appleX && this.positionY === this.appleY) {
                  this.grow();

                  this.appleX = 20 * floor(random(20));
                  this.appleY = 20 * floor(random(20));
                }
              }
            

Dernière modification: Au début du jeu, tu peux placer la pomme à un endroit aléatoire.

Tu as maintenant pratiquement fini le jeu. Bien sûr, tu peux rajouter d'autres fonctionalités, mais les bases sont là!

Ce que je te conseille est de t'attarder un peu sur les graphismes de ton jeu. En effet, la pomme peut etre rendue plus jolie, et le serpent aussi. Ton message de perte peut surement être rendu très joli aussi! À toi de personaliser ton jeu à fond!

Dans tous les cas c'est super que tu aies réussi à finir ce jeu compliqué! Bravo!