Contenu principal
Apprendre à coder
Cours : Apprendre à coder > Chapitre 4
Leçon 5: Making a memory gameJouez au jeu
OK. Nous avons donc maintenant une grille de tuiles, que nous pouvons afficher faces visibles, ou faces cachées. Mais nous n'avons aucune possibilité de jouer au jeu pour l'instant. Pour rappel, voici comment le jeu fonctionne :
- quand le jeu débute, toutes les tuiles sont faces cachées.
- le joueur tourne alors deux tuiles, en les choisissant avec un clic de souris.
- si les deux tuiles ont la même image, elles restent retournées avec leurs faces visibles. Sinon, les cartes se repositionnent faces cachées après un bref instant.
Un clic pour retourner les tuiles
Nous allons commencer par remettre toutes nos tuiles faces cachées :
for (var i = 0; i < tiles.length; i++) {
tiles[i].drawFaceDown();
}
Pour retourner une tuile, le joueur doit cliquer dessus. Pour répondre à un clic de souris dans un programme en ProcessingJS, on définit une fonction
mouseClicked
, dont le code sera exécuté à chaque fois que la souris est cliquée.mouseClicked = function() {
// code exécuté au clic de souris
};
Quand notre programme détecte que le joueur a cliqué quelque part, on souhaite savoir si il a cliqué sur une tuile, en utilisant
mouseX
et mouseY
. Commençons par ajouter une méthode isUnderMouse
("estSousLaSouris" en anglais) à Tile
, qui retourne true (vrai) si les x et y donnés sont situés à l'intérieur des limites de la tuile. De la façon dont nous avons dessiné les tuiles, les x et y de la tuile correspondent au coin supérieur gauche de celle-ci, donc nous devons retourner true seulement si x est compris entre this.x
et this.x + this.width
, et si y est compris entre this.y
et this.y + this.width
:Tile.prototype.isUnderMouse = function(x, y) {
return x >= this.x && x <= this.x + this.width &&
y >= this.y && y <= this.y + this.width;
};
Maintenant que nous avons cette méthode, nous pouvons utiliser une boucle for, à l'intérieur de
mouseClicked
, pour tester si une tuile est sous mouseX
et mouseY
. Si oui, nous la dessinons face visible :mouseClicked = function() {
for (var i = 0; i < tiles.length; i++) {
if (tiles[i].isUnderMouse(mouseX, mouseY)) {
tiles[i].drawFaceUp();
}
}
};
Voici ce que cela donne. Cliquez sur plusieurs tuiles et regardez ce qui se passe :
Avez vous remarqué quelque chose ? Nous avons implémenté un aspect du jeu, celui qui permet au joueur de retourner les tuiles, mais nous avons omis une importante restriction : on ne doit pouvoir retourner que de deux tuiles à la fois.
Il nous faut donc trouver un moyen de garder une trace du nombre de tuiles retournées quelque part. Une façon simple de procéder est l'utilisation d'une variable globale
numFlipped
, que nous incrémentons à chaque fois que le joueur retourne une carte. Nous pouvons alors omettre l'action du clic, si numFlipped
est à 2.var numFlipped = 0;
mouseClicked = function() {
for (var i = 0; i < tiles.length; i++) {
if (tiles[i].isUnderMouse(mouseX, mouseY)) {
if (numFlipped < 2) {
tiles[i].drawFaceUp();
numFlipped++;
}
}
}
};
Ce n'est pas tout à fait correct cependant. Avec ce code, que se passerait-il si on cliquait deux fois sur la même tuile de suite ? Réfléchissez. Il la retournerait "deux fois", assignerait 2 à
numFlipped
, et empêcherait tout futur retournement. Nous souhaitons plutôt, qu'il retourne la tuile, seulement si elle n'est pas face visible.Comment pouvons-nous savoir si la tuile actuelle dans la boucle for, est face cachée, ou face visible ? Nous avons sûrement dû appeler la méthode
drawFaceUp
sur cette tuile, précédemment, mais nous ne conservons pas la trace de cette information. Toc toc, bonjour, c'est moi le booléen ! Modifions les méthodes de dessin, afin de renseigner un booléen nous donnant l'état d'une tuile.Tile.prototype.drawFaceDown = function() {
// ...
this.isFaceUp = false; // carte face cachée
};
Tile.prototype.drawFaceUp = function() {
// ...
this.isFaceUp = true; // carte face visible
};
Nous pouvons donc maintenant modifier le test pour nous assurer que la tuile est bien face cachée :
if (tiles[i].isUnderMouse(mouseX, mouseY)) {
if (numFlipped < 2 && !tiles[i].isFaceUp) {
tiles[i].drawFaceUp();
numFlipped++;
}
}
Tuiles qui se retournent après un certain délai
OK. Notre logique de retournement de deux cartes maximum est fonctionnelle. Que faisons-nous maintenant ? Récapitulons de nouveau les règles du jeu :
Si les deux tuiles ont la même image, elles restent retournées avec leurs faces visibles. Sinon, les cartes se repositionnent faces cachées après un bref instant.
Nous allons tout d'abord coder la seconde partie, qui retourne automatiquement les tuiles faces cachées, car il va s'avérer difficile de tester la première partie si nous ne pouvons pas facilement retourner de nouvelles correspondances.
Nous savons comment remettre les tuiles faces cachées, en utilisant la méthode
turnFaceDown
, mais comment le faire après un certain délai ? Chaque environnement, et langage de programmation, a une approche différente, pour exécuter du code après un certain délai, et nous devons trouver celle qui correspond à ProcessingJS. Il nous faut un moyen pour conserver une trace du temps qui s'est écoulé (un certain délai), et un moyen pour appeler du code après cette période de temps. Voici ce que je propose :- Nous allons créer une variable globale appelée
delayStartFC
(début de prise en compte du délai), en l'initialisant à null. - Dans la fonction
mouseClicked
, juste après avoir retourné une deuxième tuile, nous allons affecter àdelayStartFC
la valeur actuelle deframeCount
. Cette variable indique combien il y a eu de rafraîchissement d'images depuis le commencement du programme. C'est une façon de définir le temps. - Nous allons définir une fonction
draw
, car cette fonction est appelée continuellement lors de l'exécution du programme. Elle est appelée pour chaque nouvelle valeur de frameCount. À l'intérieur, nous vérifions si cette valeur deframeCount
est sensiblement plus élevée que l'ancienne, et si oui, nous retournons toutes les tuiles faces cachées, remettonsnumFlipped
à 0, et réinitialisonsdelayStartFC
à null.
C'est en fait une solution élégante qui ne requière pas beaucoup de lignes de code. Pour une optimisation des performances, on peut utiliser les fonctions
loop
et noLoop
, pour veiller à ce que le code de dessin ne soit appelé que pendant le temps du délai. Voici l'ensemble :var numFlipped = 0;
var delayStartFC = null;
mouseClicked = function() {
for (var i = 0; i < tiles.length; i++) {
if (tiles[i].isUnderMouse(mouseX, mouseY)) {
if (numFlipped < 2 && !tiles[i].isFaceUp) {
tiles[i].drawFaceUp();
numFlipped++;
if (numFlipped === 2) {
delayStartFC = frameCount;
loop();
}
}
}
}
};
draw = function() {
if (delayStartFC && (frameCount - delayStartFC) > 30) {
for (var i = 0; i < tiles.length; i++) {
tiles[i].drawFaceDown();
}
numFlipped = 0;
delayStartFC = null;
noLoop();
}
};
Essayez-le ci-dessous. C'est plutôt sympa, la façon dont les tuiles se retournent automatiquement, vous ne trouvez pas ?
Mais nous ne voulons pas toujours que les tuiles se retournent (rappelez-vous : si deux tuiles correspondent, elles doivent rester faces visibles). Cela signifie que nous devons tester les correspondances des tuiles, à chaque fois qu'il y en a 2 de retournées, et avant d'amorcer le délai. En pseudo-code, cela se traduirait par :
si il y a deux tuiles retournées :
si la première tuile a la même image que la deuxième tuile :
conserver les tuiles faces visibles
Nous testons déjà si deux tuiles sont retournées (
numFlipped === 2
), alors comment tester si les tuiles possèdent la même image ? Tout d'abord, il nous faut un moyen d'accéder aux deux tuiles retournées. Nous pourrions itérer sur notre tableau, trouver toutes les tuiles avec isFaceUp
définie à true, et les sauvegarder dans un tableau. Ou, j'ai une meilleure idée : enregistrer en permanence nos tuiles retournées dans un tableau, pour un accès simplifié. En fait, nous pouvons remplacer numFlipped
par un tableau, et utiliser flippedTiles.length
partout où nous utilisions précédemment numFlipped
.Notre fonction
mouseClick
modifiée ressemble alors à cela :var flippedTiles = [];
var delayStartFC = null;
mouseClicked = function() {
for (var i = 0; i < tiles.length; i++) {
if (tiles[i].isUnderMouse(mouseX, mouseY)) {
if (flippedTiles.length < 2 && !tiles[i].isFaceUp) {
tiles[i].drawFaceUp();
flippedTiles.push(tiles[i]);
if (flippedTiles.length === 2) {
delayStartFC = frameCount;
loop();
}
}
}
}
};
Maintenant, il nous faut déterminer si les deux tuiles dans le tableau
flippedTiles
possèdent bien la même image. Eh bien, qu'est-ce que la propriété face
? C'est un objet image. Et en fait, les images des tuiles qui correspondent déterminent le même objet, car la variable pointe au même endroit dans la mémoire de l'ordinateur, pour les deux. C'est parce que nous créons un objet image une seule fois (comme getImage("avatars/old-spice-man"
par exemple), puis nous poussons ce même objet image, deux fois, dans le tableau faces :var face = possibleFaces[randomInd];
selected.push(face);
selected.push(face);
En Javascript, tout du moins, l'opérateur d'égalité retourne vrai s'il est utilisé sur deux variables pointant sur le même objet dans la mémoire. Ce qui signifie que notre test est simple (juste utiliser l'opérateur d'égalité sur les propriétés
face
de chaque tuile) :if (flippedTiles[0].face === flippedTiles[1].face) {
// ...
}
Maintenant que nous savons que les tuiles correspondent, il nous faut les conserver faces visibles. Pour l'instant, elles se retournent toutes après un certain délai. Nous pourrions simplement ne pas mettre en place l'animation dans ce cas précis, mais réfléchissez, il y en aura une autre plus tard. Nous ne pouvons donc pas nous reposer sur cette idée. Il nous faut plutôt un moyen de faire : "eh, quand on retourne toutes les tuiles faces cachées, on ne doit pas en retourner certaines". On dirait qu'il faut de nouveau faire appel à une bonne vieille propriété booléenne ! On peut définir une propriété
isMatch
(aTrouvéCorrespondance en français) à true (vrai) à l'intérieur de ce bloc conditionnel, et retourner les tuiles faces cachées seulement si cette propriété n'est pas vérifiée :if (flippedTiles[0].face === flippedTiles[1].face) {
flippedTiles[0].isMatch = true;
flippedTiles[1].isMatch = true;
}
for (var i = 0; i < tiles.length; i++) {
if (!tiles[i].isMatch) {
tiles[i].drawFaceDown();
}
}
Maintenant, si vous trouvez deux tuiles correspondantes ci-dessous, elles devraient rester telles quelles, une fois le délai écoulé (et pour la suite aussi) :
Gérer le score
Il ne nous reste plus qu'une chose à faire : mettre un score. Voici un rappel des règles du jeu :
Le but du jeu est de retourner toutes les cartes, faces en avant (c'est à dire, trouver toutes les paires d'images), en utilisant le moins d'essais possibles. Ce qui signifie que moins il y a de tentatives, meilleur sera le score.
Comment allons-nous conserver une trace du nombre de tentatives ? Eh bien, il y a une "tentative", chaque fois que nous retournons deux tuiles, ce qui correspond au bloc conditionnel testant
flippedTiles.length === 2
. Nous ajoutons une nouvelle variable globale, numTries
, que nous incrémentons à l'intérieur de ce bloc.if (flippedTiles.length === 2) {
numTries++;
// ...
}
On affiche le score quand le jeu est terminé (quand le joueur a trouvé toutes les paires correspondantes). Comment peut-on le vérifier ? Eh bien, nous avons un tableau avec toutes nos tuiles, dont chacune dispose d'un booléen qui peut nous dire si une correspondance a été trouvée. Nous avons donc juste à boucler dessus, et tester si ces booléens sont tous vrais. Un moyen habile d'accomplir cela, avec du code, est d'initialiser un booléen à true (vrai), avant de boucler, et d'opérer un ET logique, avec chacun des booléens de chacune des tuiles itérées. Si aucun false (faux) n'est rencontré (le tableau possède toujours au moins 1 élément !), le booléen restera vrai. Voici ce que cela donne :
var foundAllMatches = true; // (toutesLesCorrespondancesTrouvées)
for (var i = 0; i < tiles.length; i++) {
foundAllMatches = foundAllMatches && tiles[i].isMatch;
}
Quand toutes les correspondances sont trouvées, nous affichons un message de félicitation à l'utilisateur, avec le nombre de tentatives :
if (foundAllMatches) {
fill(0, 0, 0);
text("You found them all in " + numTries + " tries", 20, 360);
}
Nous allons placer tout ce code à la fin de notre fonction
mouseClicked
, pour qu'il soit exécuté après avoir testé les correspondances durant le tour.Vous pouvez essayer le code ci-dessous, mais il va peut-être vous falloir un moment avant d'obtenir un message de victoire (sans vouloir vous offenser, évidemment, parce que cela m'a pris du temps à moi aussi !). Voici une astuce à utiliser chaque fois que vous testez une partie de jeu qui est difficile à atteindre : modifiez votre code temporairement, pour que cela soit plus simple d'y arriver. Par exemple, dans ce jeu, modifier
NUM_ROWS
et NUM_COLS
et mettez de petits chiffres. Cela vous permettra de terminer beaucoup plus vite. Il est temps, maintenant, d'essayer ci-dessous !Vous souhaitez rejoindre la discussion ?
- defis de morpion,je n'arrive a choisir index sur:
// Put the player's symbol on the tile
this.label = SYMBOLS[0];
merci d'avance de votre aide...(1 vote) - this game is very fun ! merci(1 vote)