Médiaforma

All posts tagged HTML5

Ce dossier va vous montrer comment utiliser l’API JavaScript IndexedDB pour stocker des données dans une base de données locale propre au navigateur Web utilisé. IndexedDB est un système de gestion de bases de données transactionnel. Vous serez peut-être dérouté si vous avez l’habitude de travailler avec des bases de données relationnelles. En effet, IndexedDB est une base orientée objet, ce qui diffère sensiblement des tables constituées de lignes et colonnes des bases de données relationnelles…

En quelques puces, les avantages d’IndexedDB :

  • Possibilité de gérer un grand nombre de données structurées côté client, online ou offline. Recherches performantes basées sur un ou plusieurs index.
  • Fonctionnement asynchrone.

Quelques indices pour mieux cerner IndexedDB

  • Tout comme dans le Local Storage, IndexedDB mémorise des éléments sous la forme de paires clé/valeur. Par contre, les valeurs peuvent être des objets structurés (c’est-à-dire posséder une ou plusieurs propriétés) et les clés peuvent être générées automatiquement ou issues d’un key path qui définit le champ utilisé pour la clé.
  • A l’heure où j’écris ces lignes, l’API IndexedDB est essentiellement asynchrone : les données ne sont pas retournées par l’API mais par une fonction de rappel (callback). De même, on ne stocke pas et on ne lit pas des données dans la base de données. On demande de les stocker ou de les lire. Une fonction du DOM est exécutée lorsque la demande a été exécutée. Vous pouvez alors savoir si elle a abouti ou si elle a échoué.
  • IndexedDB est orienté objet : ce n’est pas une base de données relationnelle avec des tables, des colonnes et des lignes. Avec IndexedDB, on dispose d’un espace de stockage d’objets pour un type de données particulier et on manipule des objets JavaScript dans cet espace.
  • Il n’y a pas de langage SQL dans IndexedDB. Pour accéder à une donnée ou un ensemble de données, on lance une recherche sur un index. On obtient alors un curseur que l’on utilise pour parcourir l’ensemble des résultats.
  • Les requêtes sont des objets qui reçoivent les événements DOM de succès ou d’échec (propriétés onsuccess et onerror). On peut appeler addEventListener() et removeEventListener() sur ces objets. Elles ont aussi les propriétés readyState, result et errorCode qui donnent l’état d’une requête.
  • Tout ce que vous faites avec IndexedDB se fait dans le cadre de transactions, qui ont une durée de vie définie.
  • Attention : Pour des raisons de sécurité, l’accès aux données IndexedDB n’est possible qu’à partir du même domaine ou du même port que là où elles sont stockées. Si vous essayez d’y accéder depuis un autre domaine, votre demande sera refusée.

Dans la suite de la formation, vous allez apprendre à ouvrir une base de données IndexedDB, à créer un store, à écrire, lire et supprimer des données dans le store, et à afficher les résultats retournés par vos requêtes.

Attention

Alors que j’écris ces lignes, l’API IndexedDB n’est entièrement compatible qu’avec les dernières versions de Google Chrome. Les tests ont été effectués sur la version 41 de Google Chrome.

Test de compatibilité

Si Google chrome n’est pas installé sur votre ordinateur, téléchargez la dernière version de ce navigateur en vous rendant sur https://www.google.fr/chrome/browser/desktop/ :

L’API IndexedDB n’est pas finalisée. Si vous voulez la tester sur plusieurs navigateurs, vous devrez vous adresser aux différents navigateurs qui l’implémentent en utilisant des préfixes :

var window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;

Si la valeur stockée dans la variable window.indexedDB est false, le navigateur ne supporte pas (du tout ou entièrement) l’API IndexedDB :

if (!window.indexedDB) {
    window.alert("Votre navigateur n'est pas compatible avec l'API IndexedDB.")
}
else {
    window.alert("Votre navigateur est compatible avec l'API IndexedDB.")
}

Pour éviter l’affichage d’un trop grand nombre de boîtes de dialogue, nous allons insérer une balise <div> dans le code et y accéder en JavaScript pour afficher les messages. Voici le code complet :

<!DOCTYPE html>
<html>
  <head>
    <title>IndexedDB</title>
  </head>
  <body>
    <div id="note"></div>
    <script>
      var note = document.getElementById("note");

      window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
      window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
      window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;

      if (!window.indexedDB) {
        note.innerHTML += 'Votre navigateur n\'est pas compatible avec l\'API IndexedDB.';
      }
      else {
        note.innerHTML += 'Votre navigateur est compatible avec l\'API IndexedDB.';
      }
    </script>
  </body>
</html>

Préparation des données à stocker dans la base de données

Avant d’ouvrir la base de données, nous allons définir quelques données. A titre d’exemple, ces données vont concerner les personnes inscrites à une newsletter :

const inscrits = [
{ id: "01", prenom: "Pierre", nom: "Perrac", age: 28, mail: "pierre.perrac@gmail.com" },
{ id: "02", prenom: "Marie", nom: "Courteau", age: 19, mail: "marie.courteau@free.fr" }
];
note.innerHTML += 'Données préparées pour stockage dans la base de données.<br>';

Ouverture de la base de données

Pour ouvrir une base de données IndexedDB, vous utiliserez la fonction open() de l’objet window.indexedDB :

var db;
var request = window.indexedDB.open('test', 1);
request.onerror = function(evt) {
  note.innerHTML += 'Erreur : ' + evt.target.errorCode;
};
request.onsuccess = function(evt) {
  db = request.result;
  note.innerHTML += 'Base de données ouverte avec succès.<br>';
};
request.onupgradeneeded = function(event) {
}

Remarque

Si ce même code est stocké sur deux sites différents, deux bases de données portant le nom test, mais totalement différentes, seront créées dans le navigateur.

Dans cet exemple de code, la base de données à ouvrir porte le nom test. La fonction open() admet deux paramètres :

  • Le nom de la base de données. Si cette base existe, elle est ouverte. Sinon, elle est créée.
  • La version de la base de données. Ce paramètre est utilisé si la structure de la base de données change (ajout ou suppression de propriétés).

La fonction open() retourne un objet IDBRequest sur lequel on définit plusieurs fonctions événementielle :

  • onerror est lévé si l’ouverture de la base de données est impossible ;
  • onsuccess est levé si la base de données a pu s’ouvrir. Dans ce cas, l’objet lié à l’ouverture de la base de données est stocké dans la variable db.
  • onupgradeneeded est levé si vous voulez mettre à jour la base de données. Par exemple pour ajouter, supprimer ou modifier des propriétés.

Exécutez ce code dans Google Chrome. Voici ce que vous devriez obtenir :

Création d’un ObjectStore pour préparer la sauvegarde de données dans la base

Les ObjectStore sont l’équivalent des tables dans les bases de données relationnelles. Ils permettent de stocker des données (et non des tables) dans la base de données. Lorsqu’une données est stockée dans la base, elle est associée à une clé.

IndexedDB permet de créer des index sur tous les objets du store. Par leur intermédiaire, il est possible d’accéder aux valeurs stockées dans le store en utilisant la valeur d’une propriété des objets stockés.

request.onupgradeneeded = function(event) {
  var db = event.target.result;
  var objectStore = db.createObjectStore("inscrits", {keyPath: "id"});
  for (var i in inscrits) {
    objectStore.add(inscrits[i]);
  }
}

L’objet store est créé avec la méthode createObjectStore(). Cette méthode demande deux paramètres :

  • Le nom du store. Ici, le store a pour nom inscrits.
  • Un objet. Ici, nous définissons un keyPath (c’est l’équivalent d’un index) afin que chaque objet du store soit identifiable de façon unique. Vous devez vous assurer que chaque objet du store possède cette propriété.

Ajout de données dans le store

Lorsque le store a été défini, vous pouvez y ajouter des données. Pour cela, vous allez définir une transaction. Commencez par définir la fonction ajout() :

function ajout() {
}

Ajoutez un bouton dans le DOM et reliez le clic sur ce bouton à la fonction ajout() :

<body>
  <button onclick="ajout()">Ajouter une donnée dans le store</button>

Insérez le code suivant dans la fonction ajout() :

function ajout() {
//Ajout de données dans le store
var request = db.transaction(["inscrits"], "readwrite")
.objectStore("inscrits")
.add({ id: "03", prenom: "Jean", nom: "Vertec", age: 44, mail: "jean.vertec@outlook.com" });

request.onsuccess = function(event) {
note.innerHTML += 'Jean Vertec n\'a pas pu être ajouté dans le store.<br>';
};

request.onerror = function(event) {
note.innerHTML += 'Jean Vertec a été ajouté dans le store.<br>';
}
}

Pour ajouter des données dans le store, on commence par définir une transaction avec la méthode transaction() pour déterminer le nom du store concerné. La méthode transaction() admet trois paramètres :

  • La liste avec laquelle vous allez travailler. Ici, le tableau d’objets inscrits[]).
  • Le mode d’accès au store (readonly ou readwrite). Ce paramètre est optionnel.
  • Le changement de version de la base. Ce paramètre est optionnel.
var request = db.transaction(["inscrits"], "readwrite")

Une fois la transaction paramétrée, on indique le nom du store avec la fonction objectStore() :

.objectStore("inscrits")

Puis on ajoute les données avec la méthode add() :

.add({ id: "03", prenom: "Jean", nom: "Vertec", age: 44, mail: "jean.vertec@outlook.com" });

On attache deux méthodes événementielles sur les événements onsuccess et onerror à l’objet request ainsi obtenu. Ces deux méthodes affichent un message dans le <div> note :

request.onsuccess = function(event) {
note.innerHTML += 'Jean Vertec n\'a pas pu être ajouté dans le store.<br>';
};

request.onerror = function(event) {
note.innerHTML += 'Jean Vertec a été ajouté dans le store.<br>';
}

Cliquez sur le bouton. Voici ce que vous devriez obtenir :

Lecture de données dans le store

Voyons maintenant comment lire les données stockées dans le store.

Commencez par créer la fonction lecture() :

function lecture() {
}

Définissez un nouveau bouton et reliez la fonction lecture au clic sur ce bouton :

<button onclick="lecture()">Lecture et affichage du store</button>

Complétez la fonction lecture par ce code :

function lecture() {
var resultat = '';
var objectStore = db.transaction(["inscrits"]).objectStore("inscrits");
objectStore.openCursor().onsuccess = function(event) {
note.innerHTML = 'Id Prénom Nom Age Mail<br>';
var cursor = event.target.result;
if (cursor) {
resultat += cursor.key + ' : ' + cursor.value.prenom + ' ' + cursor.value.nom + ', ' + cursor.value.age + ' ans, ' + cursor.value.mail + '<br>';
cursor.continue();
}
else {
note.innerHTML += resultat;
}
};
}

Pour lire les données stockées dans le store, vous allez créer une transaction avec la méthode trasaction(). Précisez la liste avec laquelle vous allez travailler (ici, le tableau d’objets inscrits[]), puis indiquez le store sur lequel vous allez travailler avec la méthode objectStore() :

var objectStore = db.transaction(["inscrits"]).objectStore("inscrits");

Pour parcourir toutes les données dans le store, vous allez utiliser la méthode openCursor(). Si cette méthode s’exécute avec succès :

objectStore.openCursor().onsuccess = function(event) {

On commence par afficher le nom des champs dans le <div>note :

note.innerHTML = 'Id Prénom Nom Age Mail<br>';

S’il y a des données à lire dans la base :

if (cursor) {

Ces données sont lues et ajoutées à la variable resultat :

resultat += cursor.key + ' : ' + cursor.value.prenom + ' ' + cursor.value.nom + ', ' + cursor.value.age + ' ans, ' + cursor.value.mail + '<br>';

La fonction continue() est alors appelée pour poursuivre la lecture des données si d’autres données sont disponibles :

cursor.continue();
Dans le cas contraire, les données lues et mémorisées dans la variable resultat sont affichées dans le <div> note :
else {
note.innerHTML += resultat;
}

Cliquez sur le bouton Lecture et affichage du store. Voici ce que vous devriez obtenir :

Suppression de données dans le store

Pour terminer, voyons comment supprimer des données dans le store, et plus particulièrement la donnée de keypath 03, c’est-à-dire Jean Vertec.

Ajoutez la fonction suppression() :

function suppression() {
}

Définissez un bouton HTML et reliez le clic sur ce bouton à la fonction suppression() :

<button onclick="suppression()">Suppression de Jean Vertec</button>

Complétez la fonction suppression() comme ceci :

function suppression() {
var request = db.transaction(["inscrits"], "readwrite")
.objectStore("inscrits")
.delete("03");
request.onsuccess = function(event) {
note.innerHTML += 'Jean Vertec a été supprimé de la base de données.';
};
request.onerror = function(event) {
note.innerHTML += 'Jean Vertec n\'a pas pu être supprimé du store.<br>';
}
}

Vous y êtes maintenant habitué : toute opération dans le store commence par la définition d’une transaction. Pour supprimer la donnée de keypath “03”, il suffit d’utiliser la fonction delete(“03”) :

var request = db.transaction(["inscrits"], "readwrite")
.objectStore("inscrits")
.delete("03");

Les lignes suivantes définissent le code événementiel lié aux événements onsuccess et onerror. Dans chacun de ces cas, un message est affiché dans le <div> note :

request.onsuccess = function(event) {
note.innerHTML += 'Jean Vertec a été supprimé de la base de données.';
};
request.onerror = function(event) {
note.innerHTML += 'Jean Vertec n\'a pas pu être supprimé du store.<br>';
}

Cliquez sur le troisième bouton pour supprimer Jean Vertec du store puis sur le deuxième bouton pour afficher le contenu du store. Voici ce que vous devriez obtenir :

Voici le code complet :

<!DOCTYPE html>
<html>
<head>
<title>IndexedDB</title>
</head>
<body>
<button onclick="ajout()">Ajouter une donnée dans le store</button>
<button onclick="lecture()">Lecture et affichage du store</button>
<button onclick="suppression()">Suppression de Jean Vertec</button>
<div id="note"></div>
<script>
var note = document.getElementById("note");

window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;

if (!window.indexedDB) {
note.innerHTML += 'Votre navigateur n\'est pas compatible avec l\'API IndexedDB.<br>';
}
else {
note.innerHTML += 'Votre navigateur est compatible avec l\'API IndexedDB.<br>';
}

// Préparation des données à stocker dans la base de données
const inscrits = [
{ id: "01", prenom: "Pierre", nom: "Perrac", age: 28, mail: "pierre.perrac@gmail.com" },
{ id: "02", prenom: "Marie", nom: "Courteau", age: 19, mail: "marie.courteau@free.fr" }
];
note.innerHTML += 'Données préparées pour stockage dans la base de données.<br>';

// Ouverture de la base de données
var db;
var request = window.indexedDB.open('test', 1);
request.onerror = function(evt) {
note.innerHTML += 'Erreur : ' + evt.target.errorCode;
};
request.onsuccess = function(evt) {
db = request.result;
note.innerHTML += 'Base de données ouverte avec succès.<br>';

};
request.onupgradeneeded = function(event) {
// Création du store
var db = event.target.result;
var objectStore = db.createObjectStore("inscrits", {keyPath: "id"});
for (var i in inscrits) {
objectStore.add(inscrits[i]);
}
note.innerHTML += 'Store créé.<br>';
}

function ajout() {
//Ajout de données dans le store
var request = db.transaction(["inscrits"], "readwrite")
.objectStore("inscrits")
.add({ id: "03", prenom: "Jean", nom: "Vertec", age: 44, mail: "jean.vertec@outlook.com" });

request.onsuccess = function(event) {
note.innerHTML += 'Jean Vertec n\'a pas pu être ajouté dans le store.<br>';
};

request.onerror = function(event) {
note.innerHTML += 'Jean Vertec a été ajouté dans le store.<br>';
}
}
function lecture() {
var resultat = '';
var objectStore = db.transaction(["inscrits"]).objectStore("inscrits");
objectStore.openCursor().onsuccess = function(event) {
note.innerHTML = 'Id Prénom Nom Age Mail<br>';
var cursor = event.target.result;
if (cursor) {
resultat += cursor.key + ' : ' + cursor.value.prenom + ' ' + cursor.value.nom + ', ' + cursor.value.age + ' ans, ' + cursor.value.mail + '<br>';
cursor.continue();
}
else {
note.innerHTML += resultat;
}
};
}
function suppression() {
var request = db.transaction(["inscrits"], "readwrite")
.objectStore("inscrits")
.delete("03");
request.onsuccess = function(event) {
note.innerHTML += 'Jean Vertec a été supprimé de la base de données.';
};
request.onerror = function(event) {
note.innerHTML += 'Jean Vertec n\'a pas pu être supprimé du store.<br>';
}
}
</script>
</body>
</html>

Pour avoir des informations complémentaires sur IndexedDB, vous pouvez consulter les pages suivantes :


Etape 9 – Détection du nombre de coups joués et du temps en fin de partie

Nous arrivons déjà à la neuvième et dernière étape du jeu : la détection du nombre de coups joués et l’affichage du temps écoulé en fin de partie.

Commencez par définir quatre nouvelles variables dans l’en-tête du document :

var visible = new Array(0,0,0,0,0,0,0,0,0,0,0,0);

var nombre_clics = 0; // Nombre de cartes retournées

var d = new Date(); // Pour obtenir l’heure de début de partie

var heure_depart; // Heure de début de partie

var ns; // Nombre de secondes écoulées depuis le début de la partie

Pour afficher le nombre de clics pendant la partie, insérez une balise <span> d’identifiant ncr à la fin du code, juste avant la balise </body> :

<br><br>Nombre de cartes retournées : <span id=”ncr”>0</span>

</body>

Lorsque le joueur clique sur une carte, le nombre de cartes cliquées est incrémenté d’un, et le nombre de clics est affiché dans la balise <span> d’identifiant ncr :

else {

nombre_retourne++;

nombre_clics++;

document.getElementById(‘ncr’).innerHTML = nombre_clics;

Nous allons maintenant nous intéresser au temps de jeu. Commencez par mémoriser l’heure de début de jeu dans la variable heure_depart en faisant appel à la fonction getTime(). Cette instruction est insérée au début du script. Elle sera donc exécutée dès l’affichage de la page :

<script>

heure_depart = d.getTime();

Pour ceux qui voudraient en savoir plus sur la fonction getTime(), sachez qu’elle retourne le nombre de millisecondes écoulées depuis le 1 janvier 1970 à minuit.

Nous allons maintenant nous intéresser au temps nécessaire pour terminer une partie.

Commencez par ajouter une balise <span> d’identifiant temps_ecoule à la fin du document, juste avant la balise </body> :

<br><br>Nombre de cartes retournées : <span id=”ncr”>0</span>

<br><br><span id =”temps_ecoule”></span>

</body>

Pour savoir quand le jeu est terminé, nous allons mémoriser chaque appariement de cartes dans la variable nombre_apparies :

if (memo_carte == name) {  // Deux cartes identiques retournées

$(‘#bravo’)[0].play();

nombre_apparies = nombre_apparies + 2;

Pour savoir si la partie est terminée, il suffit de tester la valeur de la variable nombre_apparies. Une valeur égale à 12 indique que la partie est terminée :

if (nombre_apparies == 12) {

Dans ce cas, le son d’identifiant superbravo est émis :

$(‘#superbravo’)[0].play();

Pour calculer le temps écoulé depuis le début de la partie, nous définissons un deuxième objet Date() :

var d2 = new Date();

La quantité d2.getTime() donne le nombre de millisecondes écoulées depuis le 1 janvier 1970 à minuit. En soustrayant la valeur mémorisée au début de la partie dans la variable heure_depart et en divisant le résultat par 1000, nous obtenons le nombre de secondes écoulées depuis le début de la partie :

ns = (d2.getTime() – heure_depart) / 1000;

Il suffit maintenant d’afficher cette valeur dans la balise <span> d’identifiant temps_ecoule :

document.getElementById(‘temps_ecoule’).innerHTML = ‘Partie terminée en ‘ + ns + ‘ secondes !’;

Nous allons terminer ce jeu en ajoutant un lien dans le bas de la page pour rafraîchir la page, et donc pour recommencer une partie. Ce code doit être ajouté juste avant la balise </body> :

<br><br><a href=”memory.htm”>Recommencer le jeu</a>


Etape 8 – Ajout de sons pour réagir au jeu

Pour rendre le jeu un peu plus sympathique, nous allons lui ajouter du son. Pour cela, nous ferons appel aux balises HTML5 <audio>.

Vous pouvez télécharger des sons et effets spéciaux sur le site http://www.universal-soundbank.com/.
Internet Explorer sait lire les sons au format MP3. Quant à Chrome et Firefox, ils savent lire les sons au format OGG. Pour assurer la compatibilité avec ces trois navigateurs, vous devrez donc fournir chaque son aux formats MP3 et OGG. Le navigateur choisira le format qui lui convient.
Pour convertir des sons, je vous conseille d’utiliser le freeware Free MP3 WMA Converter en vous rendant sur la page http://www.koyotesoft.com/.

Pour insérer un son dans le document, vous utiliserez le code suivant. Ici, le son a pour identifiant « oui ». Il est fourni aux formats MP3 et OGG via deux balises <source> :

<audio preload=”auto” id=”oui”>

<source src=”oui.mp3″ type=”audio/mp3″>

<source src=”oui.ogg” type=”audio/ogg”>

</audio>

Le jeu va utiliser quatre sons d’identifiants oui, non, bravo et superbravo :

Le son d’identifiant oui sera joué lorsque le joueur clique sur une carte qui peut être rendue visible. Le son d’identifiant non sera joué lorsque le joueur essaye de rendre plus de deux cartes visibles simultanément. Le son d’identifiant bravo sera joué lorsque le joueur apparie deux cartes. Enfin, le son d’identifiant superbravo sera joué lorsque la partie est terminée.

Les balises <audio> sont insérées dans le code HTML, juste après le titre H1 :

<body>

<h1>Memory – Evaluez votre mémoire</h1>

<audio preload=”auto” id=”superbravo”>

<source src=”superbravo.mp3″ type=”audio/mp3″>

<source src=”superbravo.ogg” type=”audio/ogg”>

</audio>

 

<audio preload=”auto” id=”bravo”>

<source src=”bravo.mp3″ type=”audio/mp3″>

<source src=”bravo.ogg” type=”audio/ogg”>

</audio>

 

<audio preload=”auto” id=”oui”>

<source src=”oui.mp3″ type=”audio/mp3″>

<source src=”oui.ogg” type=”audio/ogg”>

</audio>

 

<audio preload=”auto” id=”non”>

<source src=”non.mp3″ type=”audio/mp3″>

<source src=”non.ogg” type=”audio/ogg”>

</audio>

Pour jouer un son, le plus simple consiste à utiliser une instruction jQuery. Par exemple, pour jouer le son d’identifiant oui, vous utiliserez cette instruction :

$('#oui')[0].play();

Avant de pouvoir utiliser ce type d’instruction, vous devez faire référence à la bibliothèque jQuery dans l’en-tête du document en utilisant une balise <script>.

<script src=”http://code.jquery.com/jquery-1.6.1.min.js”></script>

Ici, la bibliothèque jQuery vient du CDN jQuery. Pour ceux qui ne le sauraient pas, CDN signifie Content Delivery Network. Ce terme fait référence à des ordinateurs reliés à Internet qui mettent à disposition du code (ici jQuery) aussi rapidement que possible. Il existe trois « gros » CDN pour jQuery : jQuery, Google et Microsoft. Ici, nous avons choisi d‘utiliser le CDN jQuery, mais rien ne vous empêche de choisir un des deux autres, ou pourquoi pas, d’héberger la bibliothèque jQuery sur votre propre site.

Pour l’instant, nous allons insérer le code qui permet de jouer les sons d’identifiants oui, non et bravo. Nous nous intéresserons au son d’identifiant superbravo par la suite, lors de l’implémentation du code qui détecte la fin de la partie.

Lorsque le joueur clique sur une carte, le son d’identifiant non doit être joué si deux cartes sont déjà visibles. Dans le cas contraire, le code défini à l’étape précédente peut être exécuté :

if (nombre_retourne==2)

$(‘#non’)[0].play();

else {

Lorsqu’une carte peut être rendue visible, le son d’identifiant oui doit être joué :

else {

nombre_retourne++;

document.getElementById(id).src = name; // On montre la carte

$(‘#oui’)[0].play();

Enfin, lorsque deux cartes viennent d’être appariées, le son d’identifiant bravo doit être joué :

if (memo_carte == name) {

$(‘#bravo’)[0].play() ;

Exécutons ce code. Comme vous pouvez le constater, les trois sons sont maintenant opérationnels. Ici, nous utilisons le navigateur Google Chrome, mais le résultat est identique dans Internet Explorer 10 et Firefox.


Etape 7 – Interdiction de retourner plus de deux cartes

Pour donner un peu de piment au jeu, nous allons maintenant interdire de retourner plus de deux cartes en même temps, en ne tenant bien sûr pas compte des cartes appariées. Pour cela, nous allons comptabiliser le nombre de cartes visibles dans la variable nombre_retourne. Ajoutez cette variable dans l’en-tête du document :

<script>

var i, j, k;  // Compteurs de boucles

var nombre_retourne = 0; // Nombre de cartes retournées

Lorsque l’utilisateur clique sur une carte, le clic n’est pris en compte que dans le cas où deux cartes ne sont pas déjà retournées. Dans ce cas, le nombre de cartes retournées est incrémenté d’un :

img.onclick = function() {

if (nombre_retourne !=2) {

nombre_retourne++;

Lorsque deux cartes identiques sont identifiées, elles restent retournées jusqu’à la fin du jeu. Elles ne doivent donc plus entrer en compte dans le calcul du nombre de cartes retournées. La variable nombre_retourne est donc mise à zéro :

if (memo_carte == name) {  // Deux cartes identiques retournées

nombre_retourne=0;

Enfin, le premier paramètre de la fonction setTimeout() est ajusté pour prendre en compte la gestion de la variable nombre_retourne.

Lorsque le délai d’affichage de 1000 millisecondes est atteint, un premier test if regarde la valeur de l’élément visible de la carte concernée. Si cet élément vaut 0, il faut retourner la carte. Cela se fait en affectant la valeur dos.jpg à l’attribut src de la carte. Comme la carte est retournée, il faut aussi décrémenter d’un le nombre de cartes retournées.

Enfin, lorsque le nombre de cartes retournées est égal à zéro, il faut « oublier » la dernière carte qui a été mémorisée. Vous vous demandez peut-être pourquoi cela est nécessaire. Si c’est le cas, imaginez cette situation :

La dernière carte rendue visible représente disons … un lapin. L’utilisateur marque alors une petite pause et toutes les cartes qui étaient visibles et non appariées sont retournées. Supposons maintenant que le joueur clique sur la carte qui représente un lapin. Etant donné que la carte précédente représentait également un lapin, elle reste visible … sans pour autant être appariée. C’est un bug de jeu facilement évitable en mettant à zéro la variable memo_carte lorsqu’aucune carte non appariée n’est visible :

setTimeout(function() {if (visible[id-1]==0) {document.getElementById(id).src = ‘dos.jpg’; nombre_retourne–;} if (nombre_retourne==0) memo_carte=”;}, 1000);

Exécutons cette version du jeu.

Comme vous pouvez le voir, il est maintenant impossible d’afficher plus de deux cartes simultanément.


Etape 6 – Détection des cartes appariées

Dans cette étape, nous allons ajouter du code pour que les cartes appariées restent visibles. Pour cela, vous aurez besoin de plusieurs variables. Définissez-les dans l’en-tête du document :

<script>

var i, j, k;  // Compteurs de boucles

var memo_carte = ”; // Mémorisation du nom de la carte précédente

var memo_position = 0; // Mémorisation de la position de la carte précédente

var visible = new Array(0,0,0,0,0,0,0,0,0,0,0,0); // Indique si les cartes sont visibles (1) ou retournées (0, valeur par défaut)

Lorsque le joueur clique sur une carte il est nécessaire de la comparer à la carte qui a été retournée précédemment. Pour cela, il faut mémoriser le coup précédent. C’est la raison d’être des variables memo_carte et memo_position qui mémorisent (respectivement) le nom de la carte précédente et sa position dans le jeu.

Le tableau visible contient 12 valeurs qui représentent l’état retourné (0) ou visible (1) des cartes.

Nous allons maintenant agir sur la partie img.onclick du code :

img.onclick = function() {

document.getElementById(id).src = name; // On montre la carte

if (memo_carte == name) {  // Deux cartes identiques retournées

visible[memo_position-1] = 1;

visible[id-1] = 1;

}

else {

memo_carte = name;

memo_position = id;

setTimeout(function() {if (visible[id-1]==0) {document.getElementById(id).src = ‘dos.jpg’; }}, 1000); // On la cache après un délai de une seconde

}

}

Lors du premier clic sur une carte, memo_carte a la valeur qui lui a été affectée dans l’en-tête du document, à savoir une chaîne vide. Le test if (memo_carte == memo) n’est donc pas vérifié, et la partie else du code s’exécute. Ici, on mémorise la carte cliquée dans la variable memo_carte et sa position dans la variable memo_position :

else {

memo_carte = name;

memo_position = id;

Comme vous pouvez le voir, le premier paramètre de la fonction setTimeout() a été modifié. Lorsque les 1000 millisecondes sont écoulées, un test if s’intéresse au tableau visible, et plus particulièrement à la valeur qui correspond à la carte cliquée. Si cette valeur est égale à zéro, la carte est retournée :

if (visible[id-1]==0) {document.getElementById(id).src = ‘dos.jpg’; }

Lorsque l’utilisateur clique sur une deuxième carte, on teste si la carte cliquée est identique à celle qui avait été cliquée au tour précédent :

if (memo_carte == name) {

Dans l’affirmative, les éléments qui correspondent aux deux cartes sont mis à 1 dans le tableau visible afin que ces cartes restent visibles :

visible[memo_position-1] = 1;

visible[id-1] = 1;

Nous allons tester le code. Comme vous le voyez, les cartes appariées restent visibles :


Etape 5 – Mise en forme de la page

Dans cette étape, nous allons ajouter :

1)      Un gradient en arrière-plan de la page ;

2)      Un espace à droite de chaque carte pour obtenir un écartement régulier, horizontalement et verticalement ;

3)      Un titre au-dessus des cartes.

Le gradient est défini avec la propriété CSS3 linear-gradient. Cette propriété n’étant pas encore finalisée, il est nécessaire d’utiliser les préfixes –ms (pour Internet Explorer), -moz (pour Firefox), -o (pour Opera) et –webkit (pour Chrome et les autres navigateurs WebKit).

La propriété linear-gradient admet trois arguments :

  • Le point de départ du gradient ;
  • La couleur de départ ;
  • La couleur de fin.

Pour que l’arrière-plan utilise la propriété linear-gradient, il suffit de l’affecter à la propriété background-image de l’élément body :

body {

background-image: -ms-linear-gradient(left, #FFFFFF, #00A3EF);

background-image: -moz-linear-gradient(left, #FFFFFF, #00A3EF);

background-image: -o-linear-gradient(left, #FFFFFF, #00A3EF);

background-image: -webkit-linear-gradient(left, #FFFFFF, #00A3EF);

}

Voyons maintenant comment ajouter un espace à droite de chaque image. Pour cela, il suffit de définir la propriété margin-right des éléments img :

img {

margin-right: 5px;

}

Enfin, pour ajouter un titre au-dessus des cartes, vous insèrerez une balise <h1> juste après la balise <body> :

<body>

<h1>Memory – Evaluez votre mémoire</h1>

</body>


Etape 4 – Ajout de l’interactivité

Dans cette étape, nous allons permettre à l’utilisateur de retourner les cartes en cliquant dessus.

Pour cela, nous allons ajouter la prise en compte de l’événement onclick dans la fonction affiche_image() :

img.onclick = function() {

document.getElementById(id).src = name;

setTimeout(function() {document.getElementById(id).src = ‘dos.jpg’; } , 1000);

}

Lorsqu’une image est cliquée, la fonction affectée à img.onclick est exécutée.

La première instruction affecte le contenu de l’attribut name à l’attribut src de l’image. Rappelez-vous, l’attribut name contient une chaîne du type 1.jpg, 2.jpg, etc. qui donne le nom de la figure représentée sur la carte. En affectant cette valeur à l’attribut source de la carte, la carte apparaît sur l’écran.

La deuxième instruction retourne la carte au bout d’une seconde. Pour cela, un timer est mis en place avec la fonction setTimeout(). Cette fonction admet deux paramètres :

  • Les instructions à exécuter ;
  • Le délai en millisecondes au bout duquel ces instructions sont exécutées.

Comme vous le voyez, l’instruction exécutée au bout de 1000 millisecondes, c’est-à-dire au bout d’une seconde, cache la carte qui a été cliquée en affichant son dos.