Exercice guidé : 10 secondes

Classes Objets Évènements DOM Tableaux Boucles

Dans ce jeu, vous avez 10 secondes pour attraper autant de balles rebondissantes que vous le pouvez en cliquant dessus.

L'animation se compose de balles colorées qui se déplacent dans des directions aléatoires et rebondissent sur les bords du cadre. À chaque fois que le joueur réussit à cliquer sur une balle, le score augmente de 1. À la fin des 10 secondes, le jeu affiche "Fini" et le score.

Voici une démo :



Vous pouvez jouer en "plein écran" ici.

Pour commencer, on déclare la classe BouncingBall.


// *** Début de la classe BouncingBall ***
class BouncingBall {
	
}
							

Le constructeur ne prendra pas de paramètre puisque les caractéristiques des balles seront aléatoires. On aura besoin de plusieurs propriétés pour caractériser une balle :

  • time : le compteur de temps de vie de la balle, qui sera exprimé en millisecondes
  • dx : La position sur l'axe x de la balle, initialisée à une valeur entière au hasard entre 0 et 10
  • dy : La position sur l'axe y de la balle, initialisée également à une valeur entière au hasard entre 0 et 10
  • width : La largeur de la balle
  • height : La hauteur de la balle, qui sera donc égale à la largeur (c'est un cercle !). Cette valeur sera calculée au hasard entre 80 pixels et 160 pixels.
  • timeoutID : l'identifiant du timeout qui sera utilisé pour déplacer la balle à intervalles réguliers. On utilisera ici la fonction setTimeout qui permet d'exécuter une fonction une fois après un temps défini.
  • element : ce sera l'élément HTML représentant la balle, une div
  • playground : le terrain de jeu de la balle, l'espace dans lequel elle se déplacera

Voici le code correspondant :


    constructor () {
    	// Initialisation des paramètres de la balle
    	this.time = 0; // Pour compter les 10 secondes de "vie" de la balle
        this.dx = this.getRandomInt(0,10); // Position x de la balle
        this.dy = this.getRandomInt(0,10); // Position y
        var size = this.getRandomInt(80,160); // Diamètre de la balle (aléatoire)
        this.width = size;
        this.height = size;
        this.timeoutID; //l'ID du timeout

        // Initialisation de la div
        this.element = document.createElement('div'); // Élément qui représentera la balle
        this.element.style.backgroundColor = this.getRandomColor(); // Couleur aléatoire
        this.element.style.width = this.width + 'px'; // Dimensions de la div
        this.element.style.height = this.height + 'px';
        this.element.className += 'ball'; // Pour le CSS

        this.playground = document.getElementById('playground');
        this.playground.appendChild(this.element); //Ajout de la balle à la div #playground

        // TODO Tracé de la balle
        // TODO Écouteur des clics sur la balle
    }
							

Quand on aura écrit les méthodes correspondantes, on ajoutera un appel à la méthode permettant de dessiner la balle, ainsi que l'écouteur d'évènements sur la balle.

Vous pouvez voir que j'ai fait appel à deux méthodes getRandomInt et getRandomColor qui pour la première tirera un nombre entier au hasard et pour la seconde une couleur au hasard. Ils nous faut donc écrire ces deux méthodes :


	getRandomColor(){
    // Retourne un code couleur au hasard dans le modèle hsla
    	return 'hsla(' + (Math.random() * 360) + ', 100%, 50%, 1)';
    }

    getRandomInt(min, max){
    // Retourne un nombre entier aléatoire entre min et max
    	return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    					

Ensuite, pour déplacer une balle, on aura besoin de modifier le style de balle : top pour le positionnement sur l'axe x et left pour le positionnement sur l'axe y. On va donc écrire une méthode moveTo prenant en paramètre les coordonnées x et y où déplacer la balle et qui modifiera les propriétés correspondantes, comme ceci :


    moveTo(x, y) {
    // Modifie les coordonnées x et y de la balle
        this.element.style.left = x + 'px';
        this.element.style.top = y + 'px';
    }
							

Il nous reste maintenant deux tâches à programmer : le tracé de la balle à intervalles réguliers et le changement de direction quand la balle atteint les limites du cadre. Regardons le changement de direction. On nommera la méthode changeDirectionIfNecessary et elle prendra en paramètre les coordonnées x et y de la balle. Si la position x devient négative (déborde du cadre à gauche) ou supérieure à la largeur du cadre, on inverse le déplacement en changeant le signe de la coordonnée x. On procède de la même façon pour l'axe y. Voici le résultat :


    changeDirectionIfNecessary(x, y) {
    // Modifie la direction de la balle quand elle atteint les limites du terrain
    	// Limites gauche/droite
        if (x < 0 || x > this.playground.getBoundingClientRect().width - this.width) {
            this.dx = -this.dx; //demi-tour
        }
        // Limites bas/haut
        if (y < 0 || y > this.playground.getBoundingClientRect().height - this.height) {
            this.dy = -this.dy;
        }
    }
							

Il nous reste à tracer la balle à intervalles réguliers en faisant appel aux méthodes que l'on a déjà écrites. Ce sera le rôle de la méthode draw. Si la balle est "en vie" depuis moins de 10 secondes, on rappelera à nouveau la méthode draw après 18ms avec la fonction setTimeout, en changeant de direction si nécessaire. Sinon on interrompra le tracé de la balle et on affichera un message "Fini" dans la zone du jeu. La fonction setTimeout fonctionne différemment de setInterval : elle appelle la fonction cible une seule fois, après un temps défini (ici 18ms). Regardez bien le code : notre méthode draw appelle la fonction setTimeout qui elle-même appelle la méthode draw qui rappelera donc la fonction setTimeout et ainsi de suite : le code est donc répété pendant 10 secondes. Notez l'utilisation de bind(this) : on veut que this représente notre balle lorsque la fonction est exécutée.


    draw(x, y) {
    // Dessine la balle aux coordonnées x et y toutes les 18ms
    	this.time += 18; /// Le temps passe...
    	if (this.time <= 10000){ // Si le jeu est commencé depuis moins de 10s
	        this.moveTo(x, y); // On modifie les coordonnées

	        this.timeoutID = setTimeout(function () {
	            this.changeDirectionIfNecessary(x, y); // Chgt de direction
	            this.draw(x + this.dx, y + this.dy); // Tracé
	        }.bind(this), 18); // Et ainsi de suite toutes les 18ms
	    }
	    else { // Si le jeu est fini
	    	clearTimeout(this.timeoutID); 
	    	this.playground.innerHTML = "

Fini

"; } }

Il nous faut maintenant compléter le constructeur. À la création d'une balle, on appelera directement la méthode draw pour débuter l'animation avec des coordonnées x et y aléatoires. On doit également implémenter l'écouteur d'évènements. Lorsque la souris est pressée sur la div représentant la balle (le joueur a "attrapé" la balle), on met à jour son score dans l'élement HTML correspondant sur la page, on supprime la balle du DOM et on arrête son déplacement. Il ne faut pas oublier ici non plus d'utiliser la méthode bind en lui donnant en paramètre this puisqu'on veut que this représente notre balle dans le corps de la fonction. Voici le code :


	constructor () {
		.
		.
		.
        // Tracé de la balle
        this.draw(this.getRandomInt(0,300), this.getRandomInt(0,300)) //Vers des coordonnées au hasard

        // Écouteur des clics sur la balle
        this.element.addEventListener("mousedown", function(){
        	// Mise à jour du score
			var total = parseInt(document.getElementById("score").textContent) +1;
			document.getElementById("score").textContent = total;
			// On retire la balle
			this.element.remove();
			// On arrête le déplacement
			clearTimeout(this.timeoutID);
		}.bind(this));
	}
							

Et pour finir, on va créer toutes les balles du jeu à partir de notre classe. Ici j'en crée 30, à l'aide d'une boucle. Chaque balle créée aura donc une couleur et des positions et déplacements aléatoires.


// *** Code principal ***
var NB_BALLS = 30; // Nombre de balles dans le jeu
var balls = []; // Tableau des balles

for (var i = 0; i < NB_BALLS; i++) { // Génération des balles
  balls[i] = new BouncingBall();
}
							

Et voici le programme au complet :



Aller au TP