Comment créer un jeu Simon avec JavaScript
ns ce didacticiel, nous allons développer le jeu Simon classique avec JavaScript. Le but du jeu est de répéter une série de clics de tuiles aléatoires créés par le jeu. Après chaque tour, la séquence devient progressivement plus longue et plus complexe, ce qui la rend plus difficile à retenir.

En règle générale, vous aurez quatre tuiles différentes, chacune avec une couleur et un son uniques qui s’activent lorsque vous appuyez dessus. Le son aide le joueur à se souvenir de la séquence et le jeu se termine si le joueur manque une étape de la séquence.
Conditions préalables
Ce tutoriel suppose une connaissance de base de JavaScript et du DOM .
Commencer
Récupérez les fichiers HTML et CSS du jeu sur GitHub . Les instructions pour configurer le projet se trouvent dans le fichier README inclus. Vous pouvez également suivre le tutoriel en utilisant JSFiddle si vous préférez.

Comme mentionné précédemment, un tour commence lorsque le jeu active une ou plusieurs tuiles dans un ordre aléatoire et se termine lorsque le joueur reproduit l’ordre en appuyant sur les tuiles. Au tour suivant, le nombre de tuiles dans la séquence augmente de un.
Commençons par créer un tableau pour garder une trace de la séquence originale de clics sur les tuiles et un second tableau pour la séquence humaine :
let sequence = [];
let humanSequence = [];
Ensuite, sélectionnez le bouton de démarrage et créez une nouvelle startGame()
fonction qui sera exécutée lorsque ce bouton sera cliqué.
const startButton = document.querySelector('.js-start');
function startGame() {
startButton.classList.add('hidden');
}
startButton.addEventListener('click', startGame);
À ce stade, une fois le bouton de démarrage enfoncé, il sera masqué. L’ .info
élément doit également apparaître car c’est là que les messages d’état seront affichés. Il a la hidden
classe appliquée par défaut dans le HTML qui a été stylisé display: none;
dans le CSS , c’est donc ce qui doit être supprimé une fois le jeu démarré.
<span class="info js-info hidden"></span>
Mettez à jour votre fichier JavaScript comme indiqué ci-dessous :
const startButton = document.querySelector('.js-start');
const info = document.querySelector('.js-info');
function startGame() {
startButton.classList.add('hidden');
info.classList.remove('hidden');
info.textContent = 'Wait for the computer';
}
Désormais, une fois le bouton de démarrage cliqué, l’ .info
élément affiche un message indiquant à l’utilisateur d’attendre la fin de la séquence.
Commencer le tour suivant
Créez une nouvelle level
variable ci-dessous humanSequence
. C’est ainsi que nous garderons une trace du nombre de tours qui ont été joués jusqu’à présent.
let level = 0;
Ensuite, créez une nouvelle nextRound()
fonction juste au-dessus startGame()
, comme indiqué dans l’extrait ci-dessous. Le but de cette fonction est de démarrer la prochaine séquence de clics sur les tuiles.
function nextRound() {
level += 1;
// copy all the elements in the `sequence` array to `nextSequence`
const nextSequence = [...sequence];
}
A chaque nextRound()
appel, la level
variable est incrémentée de 1 et la séquence suivante est préparée. Chaque nouveau tour s’appuie sur le précédent, donc ce que nous devons faire est de copier l’ordre existant des pressions sur les boutons et d’y ajouter un nouveau aléatoire. Nous avons fait le premier sur la dernière ligne de nextRound()
, alors ajoutons maintenant une nouvelle pression de bouton aléatoire à la séquence.
Créez une nouvelle nextStep()
fonction juste au dessus nextRound()
:
function nextStep() {
const tiles = ['red', 'green', 'blue', 'yellow'];
const random = tiles[Math.floor(Math.random() * tiles.length)];
return random;
}
La tiles
variable contient les couleurs de chaque bouton du plateau de jeu. Remarquez comment les valeurs correspondent aux valeurs de la data-tile
propriété dans le HTML .
<div class="tile tile-red" data-tile="red"></div>
<div class="tile tile-green" data-tile="green"></div>
<div class="tile tile-blue" data-tile="blue"></div>
<div class="tile tile-yellow" data-tile="yellow"></div>
Nous devons obtenir une valeur aléatoire du tableau à chaque nextStep()
exécution, et nous pouvons y parvenir en utilisant la Math.random()
fonction en combinaison avec Math.floor()
. Le premier renvoie un nombre pseudo-aléatoire à virgule flottante compris entre 0 et moins de 1.

Cela ne nous est pas très utile pour le moment. Il doit être converti en un index valide pour le tiles
tableau (0, 1, 2 ou 3 dans ce cas) afin qu’une valeur aléatoire provenant du tableau puisse être récupérée à chaque fois. Multiplier la valeur de Math.random()
par la longueur de tiles
(qui est 4) garantit que la plage du nombre aléatoire est maintenant comprise entre 0 et moins de 4 (au lieu de 0 et moins de 1).

Pourtant, ces valeurs fractionnaires ne sont pas des index de tableau valides, elles Math.floor()
sont donc utilisées pour arrondir les nombres au plus grand entier inférieur ou égal à la valeur donnée. Cela nous donne des entiers entiers entre 0 et 3 qui peuvent être utilisés pour récupérer une valeur aléatoire du tiles
tableau.

Utilisons la valeur de retour de la nextStep()
fonction dans nextRound()
comme indiqué ci-dessous :
function nextRound() {
level += 1;
const nextSequence = [...sequence];
nextSequence.push(nextStep());
}
Ce qui se passe ici, c’est que lorsqu’il nextStep()
est exécuté, il renvoie une valeur aléatoire du tiles
tableau (« rouge », « bleu », « vert » ou « jaune »), et la valeur est ajoutée à la fin du nextSequence()
tableau à côté de toutes les valeurs du tour précédent.
Jouer le tour suivant
L’étape suivante consiste à jouer le tour suivant en activant les tuiles à l’écran dans le bon ordre. Ajoutez les fonctions suivantes ci-dessus nextStep()
dans votre fichier JavaScript :
function activateTile(color) {
const tile = document.querySelector(`[data-tile='${color}']`);
const sound = document.querySelector(`[data-sound='${color}']`);
tile.classList.add('activated');
sound.play();
setTimeout(() => {
tile.classList.remove('activated');
}, 300);
}
function playRound(nextSequence) {
nextSequence.forEach((color, index) => {
setTimeout(() => {
activateTile(color);
}, (index + 1) * 600);
});
}
La playRound()
fonction prend un tableau de séquences et itère dessus. Il utilise ensuite la setTimeout()
fonction pour appeler le activateTile()
à des intervalles de 600 millisecondes pour chaque valeur de la séquence. La raison setTimeout()
utilisée ici est d’ajouter un délai artificiel entre chaque pression sur un bouton. Sans cela, les tuiles de la séquence seront activées toutes en même temps.
Le nombre de millisecondes spécifié dans la setTimeout()
fonction change à chaque itération. Le premier bouton de la séquence est activé après 600 ms, le suivant après 1200 ms (600 ms après le premier), le troisième après 1800 ms, etc.
Dans la activateTile()
fonction, la valeur de color
est utilisée pour sélectionner la vignette et les éléments audio appropriés. Dans le fichier HTML , notez comment l’ data-sound
attribut sur les audio
éléments correspond aux couleurs des boutons.
<audio src="https://s3.amazonaws.com/freecodecamp/simonSound1.mp3" data-sound="red" ></audio>
<audio src="https://s3.amazonaws.com/freecodecamp/simonSound2.mp3" data-sound="green" ></audio>
<audio src="https://s3.amazonaws.com/freecodecamp/simonSound3.mp3" data-sound="blue" ></audio>
<audio src="https://s3.amazonaws.com/freecodecamp/simonSound4.mp3" data-sound="yellow" ></audio>
La activated
classe est ajoutée à la vignette sélectionnée et la play()
méthode est déclenchée sur l’élément audio sélectionné, provoquant la lecture du fichier mp3 lié dans l’ src
attribut. Après 300 millisecondes, la activated
classe est à nouveau supprimée. L’effet est que chaque tuile est activée pendant 300 ms, et il y a 300 ms entre les activations de tuiles dans la séquence.
Enfin, appelez playRound()
la nextRound()
fonction et appelez nextRound()
la startGame()
fonction comme indiqué ci-dessous :
function nextRound() {
level += 1;
const nextSequence = [...sequence];
nextSequence.push(nextStep());
playRound(nextSequence);
}
function startGame() {
startButton.classList.add('hidden');
info.classList.remove('hidden');
info.textContent = 'Wait for the computer';
nextRound();
}
Maintenant, une fois que vous avez appuyé sur le bouton de démarrage, le premier tour commencera et un bouton aléatoire sera activé sur le tableau.

Au tour du joueur
Une fois que l’ordinateur commence un tour en activant la prochaine séquence de tuiles, le joueur doit terminer le tour en répétant le schéma d’activation des tuiles dans le bon ordre. Si une étape est manquée en cours de route, le jeu se termine et se réinitialise.
Sélectionnez les éléments de titre et de conteneur de tuiles sous la info
variable :
const heading = document.querySelector('.js-heading');
const tileContainer = document.querySelector('.js-container');
Ensuite, créez une humanTurn
fonction qui indique que l’ordinateur a terminé le tour et qu’il est temps pour le joueur de répéter la séquence :
function humanTurn(level) {
tileContainer.classList.remove('unclickable');
info.textContent = `Your turn: ${level} Tap${level > 1 ? 's' : ''}`;
}
La première étape consiste à supprimer la unclickable
classe du conteneur de tuiles. Cette classe empêche d’appuyer sur les boutons lorsque le jeu n’a pas démarré et lorsque l’ IA n’a pas terminé la séquence d’appuis.
.unclickable {
pointer-events: none;
}
Sur la ligne suivante, le contenu de l’ info
élément est modifié pour indiquer que le joueur peut commencer à répéter la séquence. Il indique également le nombre de frappes à saisir.
La humanTurn()
fonction doit être exécutée une fois la séquence de l’ordinateur terminée, nous ne pouvons donc pas l’appeler immédiatement. Nous devons ajouter un délai artificiel et calculer quand l’ordinateur en aura terminé avec la séquence de pressions sur les boutons.
function nextRound() {
level += 1;
const nextSequence = [...sequence];
nextSequence.push(nextStep());
playRound(nextSequence);
sequence = [...nextSequence];
setTimeout(() => {
humanTurn(level);
}, level * 600 + 1000);
}
La setTimeout()
fonction ci-dessus s’exécute humanTurn()
une seconde après l’activation du dernier bouton de la séquence. La durée totale de la séquence correspond au niveau actuel multiplié par 600 ms qui est la durée de chaque tuile de la séquence. La sequence
variable est également affectée à la séquence mise à jour.
Dans la prochaine mise à jour de nextRound()
, la unclickable
classe est ajoutée au conteneur de vignettes au début de la partie, et le contenu des éléments info
et heading
est mis à jour.
function nextRound() {
level += 1;
tileContainer.classList.add('unclickable');
info.textContent = 'Wait for the computer';
heading.textContent = `Level ${level} of 20`;
const nextSequence = [...sequence];
nextSequence.push(nextStep());
playRound(nextSequence);
sequence = [...nextSequence];
setTimeout(() => {
humanTurn(level);
}, level * 600 + 1000);
}

L’étape suivante consiste à détecter les appuis sur les boutons du joueur et à décider s’il faut passer au tour suivant ou mettre fin à la partie. Ajoutez l’écouteur d’événement suivant juste en dessous de celui pourstartButton
:
tileContainer.addEventListener('click', event => {
const { tile } = event.target.dataset;
if (tile) handleClick(tile);
});
Dans l’écouteur d’événement ci-dessus, la valeur de data-tile
sur l’élément sur lequel on a cliqué est accessible et stockée dans la tile
variable. Si la valeur n’est pas une chaîne vide (pour les éléments sans l’ data-tile
attribut), la handleClick()
fonction est exécutée avec la tile
valeur comme seul argument.
Créez la handleClick()
fonction juste au-dessus startGame()
comme indiqué ci-dessous :
function handleClick(tile) {
const index = humanSequence.push(tile) - 1;
const sound = document.querySelector(`[data-sound='${tile}']`);
sound.play();
const remainingTaps = sequence.length - humanSequence.length;
if (humanSequence.length === sequence.length) {
humanSequence = [];
info.textContent = 'Success! Keep going!';
setTimeout(() => {
nextRound();
}, 1000);
return;
}
info.textContent = `Your turn: ${remainingTaps} Tap${
remainingTaps > 1 ? 's' : ''
}`;
}
Cette fonction pousse la valeur de tuile dans le humanSequence
tableau et stocke son index dans la index
variable. Le son correspondant au bouton est joué et les étapes restantes de la séquence sont calculées et mises à jour à l’écran.
Le if
bloc compare la longueur du humanSequence
tableau au sequence
tableau. S’ils sont égaux, cela signifie que le tour est terminé et que le tour suivant peut commencer. À ce stade, le humanSequence
tableau est réinitialisé et la nextRound()
fonction est appelée après une seconde. Le délai permet à l’utilisateur de voir le message de réussite, sinon il n’apparaîtra pas du tout car il sera immédiatement écrasé.

Comparez les séquences
Nous devons comparer l’ordre dans lequel le joueur appuie sur les boutons à l’ordre de la séquence générée par le jeu. Si l’ordre ne correspond pas, le jeu se réinitialise et un message s’affiche pour alerter le joueur de l’échec.
Créez une nouvelle resetGame()
fonction à cet effet ci-dessus humanTurn()
:
function resetGame(text) {
alert(text);
sequence = [];
humanSequence = [];
level = 0;
startButton.classList.remove('hidden');
heading.textContent = 'Simon Game';
info.classList.add('hidden');
tileContainer.classList.add('unclickable');
}
Cette fonction affiche une alerte et restaure le jeu à son état d’origine. Utilisons-le handleClick
comme indiqué ci-dessous :
function handleClick(tile) {
const index = humanSequence.push(tile) - 1;
const sound = document.querySelector(`[data-sound='${tile}']`);
sound.play();
const remainingTaps = sequence.length - humanSequence.length;
if (humanSequence[index] !== sequence[index]) {
resetGame('Oops! Game over, you pressed the wrong tile');
return;
}
if (humanSequence.length === sequence.length) {
humanSequence = [];
info.textContent = 'Success! Keep going!';
setTimeout(() => {
nextRound();
}, 1000);
return;
}
info.textContent = `Your turn: ${remainingTaps} Tap${
remainingTaps > 1 ? 's' : ''
}`;
}
Si la valeur de l’élément récupéré par le index
dans les tableaux sequence
et humanSequence
ne correspond pas, cela signifie que le joueur a fait un mauvais tour. À ce stade, une alerte s’affiche et le jeu se réinitialise.
Préparer un état final
Le jeu fonctionne principalement mais nous devons introduire un état final où le joueur gagne la partie. J’ai choisi 20 tours, mais vous pouvez utiliser n’importe quel nombre que vous aimez. Le Simon Game classique s’est terminé après 35 tours.
Voici le bit qui met fin au jeu si l’utilisateur atteint et termine le 20e tour :
function handleClick(tile) {
const index = humanSequence.push(tile) - 1;
const sound = document.querySelector(`[data-sound='${tile}']`);
sound.play();
const remainingTaps = sequence.length - humanSequence.length;
if (humanSequence[index] !== sequence[index]) {
resetGame('Oops! Game over, you pressed the wrong tile');
return;
}
if (humanSequence.length === sequence.length) {
if (humanSequence.length === 20) {
resetGame('Congrats! You completed all the levels');
return
}
humanSequence = [];
info.textContent = 'Success! Keep going!';
setTimeout(() => {
nextRound();
}, 1000);
return;
}
info.textContent = `Your turn: ${remainingTaps} Tap${
remainingTaps > 1 ? 's' : ''
}`;
}
Une fois le 20e tour terminé, un message de félicitations s’affiche et le jeu se réinitialise. Assurez-vous de l’essayer et voyez si vous pouvez atteindre le niveau 20 sans échouer.