Le Démineur JavaScript (N4/IE4) 

 

 

 

C'est à une réalisation difficile que Yazo.net vous convie cette fois et seuls les plus férus de code voudront suivre toutes les explications ci dessous; les autres étudieront seulement la logique de construction du jeu pour la faire servir à d'autre programmation, Lingo par exemple.

La réalisation de ce jeu de démineur est aussi un prétexte pour découvrir les nouvelles possibilités JavaScript de gestion des événements clavier puisque, pour marquer une case que l'on soupçonne de cacher une mine, il faut procéder par Alt/Option clic. Cette simple nouveauté mérite bien votre attention. On commence ?

 

Création du jeu

...

Immédiatement après la préparation des images à l'aide de Photoshop, on commence par la génération du plateau de jeu. Nous saisissons le code directement dans le bloc-notes de Windows ou dans SimpleText. Le plateau c'est 100 images disposées cote à cote ! JavaScript ici nous épargne d'avoir à saisir un code HTML redondant, en mettant à notre disposition une boucle incluant l'instruction document.write(). Après chaque série de 10 images, on insère un saut de ligne <BR> . Enfin on n'oublie pas de fermer l'input stream du navigateur avec document.close().

<HTML><HEAD><SCRIPT LANGUAGE="JavaScript">

function definitLePlateau() {
document.write("<BODY><BR><BR><H4>");
document.write("<FONT COLOR=red>ATTENTION AUX MINES !</FONT></H4>");
document.write("<BR><BR>");

for (ligne=0 ; ligne <10 ; ligne++) {
   for (colonne = 0 ; colonne < 10 ; colonne++) {
    document.write("<IMG SRC='dessus.gif' BORDER=0 WIDTH=12 HEIGHT=12>");
    }
    document.write("<BR>");
}
document.write("</BODY>");
document.close();
}

Immédiatement après cette déclaration de fonction, nous l'appelons simplement :

definitLePlateau();

</SCRIPT> </HEAD> </HTML>

 

En l'état, notre code peut déjà être testé dans le navigateur. On enregistre la page avec une extension .HTML dans le même dossier que les images et on l'ouvre dans le client Web. Si tout se passe bien, on peut continuer notre travail et enrichir le code source.

En effet, en même temps que se construit "réellement" le plateau en HTML, il nous faut créer un plateau virtuel des cases "de dessous", cases qui se dévoileront à chaque clic de l'utilisateur sur une image.

Ces clics de l'utilisateur justement. Ils ne seront interceptés que si chaque image du plateau réel est un hyperlien <A HREF=...> auquel on a associé un gestionnaire d'événement "onClick" et une fonction, ici "doIt()".

La fonction aura pour vocation d'intercepter et de répondre au clic et même, on le verra, au Alt-clic. Afin d'empêcher que le clic ait d'autre conséquence, comme l'ouverture d'un dialogue d'enregistrement, on ajoute à ce gestionnaire d'événement l'instruction "return false". Dès lors l'événement n'aura pas d'effets non souhaités.

Pour préparer la rédaction à venir de la fonction "doIt()", nous veillons aussi à lui passer quelques paramètres : "event" qui définit l'évènement (nous en auront besoin pour tester le clavier), ainsi que les numéro de ligne et de colonne de l'image à chaque fois cliquée. Notre fonction definitLePlateau() est donc complétée (en rouge) comme suit :

//   création du plateau virtuel - un tableau de 100 entrées :

var plateau = new Array(100);
for (x = 0 ; x < 100 ; x++) {
plateau[x] = null;
}

function definitLePlateau() {
document.write("<BODY><BR><BR><H4>");
document.write("<FONT COLOR=red>ATTENTION AUX MINES !</FONT></H4>");
document.write("<BR><BR>");

for (ligne=0 ; ligne <10 ; ligne ++) {
   for (colonne = 0 ; colonne < 10 ; colonne ++) {
     plateau[ligne *10 + colonne ] = "dessous.gif";
   
 // chaque case du plateau virtuel est pour l'instant une case vide :"dessous.gif"
    
    document.write("<A HREF='#' onClick='doIt(event, " + ligne + ", " + colonne + "); return false'><IMG SRC='dessus.gif' BORDER=0 WIDTH=12 HEIGHT=12></A>");
    }
    document.write("<BR>");
  }
document.write("</BODY>");
document.close();
}

 

Notre mise en place est-elle suffisante ? Il y manque juste un détail ! Les case du plateau virtuel que les clics dévoileront grâce à la fonction doIt() sont toutes identiques, aucune de ces cases ne contient de mines ! Nous ajoutons une nouvelle instruction placeLesMinesAuHasard() à definitLePlateau() :

var plateau = new Array(100);
for (x = 0 ; x < 100 ; x++) {
plateau[x] = null;
}

function definitLePlateau() {
document.write("<BODY><BR><BR><H4>");
document.write("<FONT COLOR=red>ATTENTION AUX MINES !</FONT></H4>");
document.write("<BR><BR>");

for (ligne=0 ; ligne <10 ; ligne ++) {
   for (colonne = 0 ; colonne < 10 ; colonne ++) {
     plateau[ligne *10 + colonne ] = "dessous.gif";
    // chaque case du plateau virtuel est pour l'instant une case vide "dessous.gif"

    document.write("<A HREF='#' onClick='doIt(event, " + ligne + ", " + colonne + "); return false'><IMG SRC='dessus.gif' BORDER=0 WIDTH=12 HEIGHT=12></A>");
    }
    document.write("<BR>");
  }
placeLesMinesAuHasard (6)
document.write("</BODY>");
document.close();
}

 

Et comment allons-nous placer les mines aux hasard ? Tout d'abord en nous dotant d'une fonction susceptible de retourner un nombre aléatoire entre 0 et 99. Observez bien la manœuvre car elle offre un moyen simple de contourner les incompatibilités de Math.random() : On se donne une date convertie en millièmes de seconde via la méthode getTime(), le nombre ainsi obtenu est divisé par cent, on peut dès lors récupérer la fraction décimale obtenue (les deux chiffres après la virgule ou modulo de la division) pour simuler un nombre aléatoire entre 00 et 99.

function unNombreAuHasard() {
maintenant = new Date();
unChiffreEntreZeroEtQutreVingtDixNeuf = (maintenant.getTime()/10)%100;
return unChiffreEntreZeroEtQutreVingtDixNeuf;
}

Notre fonction placeLesMinesAuHasard() recevra en argument (combien) le nombre de mines à disperser. Autant de fois que demandé, la fonction prend au hasard, une entrée du plateau virtuel et la remplace par une image de bombe s'il y a lieu (c'est-à-dire si ça n'est pas déjà fait). Afin de s'assurer qu'on aura bien le nombre de mines demandé, la fonction lorsqu'elle rencontre par hasard une image déjà modifiée, décrémente son compteur "n -- " et recommence le tirage au sort.

 

function placeLesMinesAuHasard(combien) {
for( n =0 ; n < combien ; n++) {
    i = unNombreAuHasard();
          if (plateau[i]=="dessous.gif") {
                 plateau[i] = "bomb.gif";
         }
         else {
         n--;
        continue;
        }
  }
}

 

Mais dans le vrai jeu de démineur, chaque mine est entourée de numéros qui permettent de deviner sa présence. Nous devons donc modifier d'avantage le plateau virtuel et nous occuper aussi des cases qui entourent chacune des mines. On ajoute :

function placeLesMinesAuHasard(combien) {
for( n =0 ; n < combien ; n++) {
    i = unNombreAuHasard();
          if (plateau[i]=="dessous.gif") {
                 plateau[i] = "bomb.gif";
                 occupeToiDesImagesAutourDe(i);
         }
         else {
         n--;
        continue;
        }
  }
}

 

Et on passe aux choses sérieuses !

Chaque mine sur le plateau virtuel occupe une case et est donc environnée de 3, 5 ou 8 autres cases. Afin de changer ces cases alentour, nous devons nous doter d'un moyen de les identifier. Nous créons une fonction lesImagesAutourDe() qui renverra une liste (lesquelles) des cases environnantes pour toute case donnée.

 

function lesImagesAutourDe(celleLa) {
lesquelles = new Array(3);
// on considère d'abord les cases des bords
if (celleLa < 10 || celleLa > 89 || celleLa%10 == 0 || celleLa%10 == 9 || celleLa==0) {

if (celleLa==0) {
lesquelles.length = 3;
lesquelles[0] = celleLa + 1;
lesquelles[1] = celleLa + 10;
lesquelles[2] = celleLa + 11;
}

if ( celleLa== 9) {
lesquelles.length = 3;
lesquelles[0] = celleLa - 1;
lesquelles[1] = celleLa + 9;
lesquelles[2] = celleLa + 10;
}

if (celleLa == 90) {
lesquelles.length = 3;
lesquelles[0] = celleLa - 10;
lesquelles[1] = celleLa - 9;
lesquelles[2] = celleLa + 1;
}

if (celleLa == 99) {
lesquelles.length = 3;
lesquelles[0] = celleLa - 11;
lesquelles[1] = celleLa - 10;
lesquelles[2] = celleLa - 1;
}

if (celleLa%10 == 0 && celleLa != 0 && celleLa != 90) {  // rangée de gauche
lesquelles.length = 5;
lesquelles[0] = celleLa - 10;
lesquelles[1] = celleLa - 9;
lesquelles[2] = celleLa + 1;
lesquelles[3] = celleLa + 10;
lesquelles[4] = celleLa + 11;
}

if (celleLa%10 == 9 && celleLa != 9 && celleLa != 99) {  // rangée de droite
lesquelles.length = 5;
lesquelles[0] = celleLa - 11;
lesquelles[1] = celleLa - 10;
lesquelles[2] = celleLa - 1;
lesquelles[3] = celleLa + 9;
lesquelles[4] = celleLa + 10;
}

if (celleLa > 89 && celleLa != 90 && celleLa != 99) {  // rangée du bas
lesquelles.length = 5;
lesquelles[0] = celleLa - 11;
lesquelles[1] = celleLa - 10;
lesquelles[2] = celleLa - 9;
lesquelles[3] = celleLa -1;
lesquelles[4] = celleLa + 1;
}

if (celleLa < 10 && celleLa != 0 && celleLa != 9) {  // rangée du haut
lesquelles.length = 5;
lesquelles[0] = celleLa - 1;
lesquelles[1] = celleLa + 1;
lesquelles[2] = celleLa + 9;
lesquelles[3] = celleLa +10;
lesquelles[4] = celleLa + 11;
}
}
else                       // toutes les autres cases entourées de 8
{
lesquelles.length = 8;
lesquelles[0] = celleLa - 11;
lesquelles[1] = celleLa - 10;
lesquelles[2] = celleLa - 9;
lesquelles[3] = celleLa - 1;
lesquelles[4] = celleLa + 1;
lesquelles[5] = celleLa + 9;
lesquelles[6] = celleLa + 10;
lesquelles[7] = celleLa + 11;
}

return lesquelles;
}

 

Dès lors notre fonction occupeToiDesImagesAutourDe(uneImage) peut connaître le nombre d'images à traiter pour chaque mine (propriété lesImagesAutourDe(uneMine).length) et même accéder à chacune d'entre elles (via sa position dans la liste : lesImagesAutourDe(uneImage)[position]). Notre fonction s'écrira :

function occupeToiDesImagesAutourDe(uneImage) {
for (z = 0 ;  z < lesImagesAutourDe(uneImage).length ;  z++) {
    changeImage(lesImagesAutourDe(uneImage)[z]);
   }
}

Ici, changeImage() est une fonction (une de plus) qui se charge de modifier les image adjacentes à une mine.

Ce que changeImage() doit faire est simple : Pour chaque image à coté d'une mine, si cette image est une case neutre ("dessous.gif"), la remplacer par un "un" sauf si, du fait d'un remplacement précédemment effectué, cette image est déjà un "un", c'est-à-dire si elle a déjà été identifiée comme adjacente à une autre mine, alors, la remplacer par un "deux", sauf si c'est déjà un "deux", auquel cas le remplacer par un "trois", sauf si, etc, etc. Pour mécaniser ce processus de test répétitif, on se donne un tableau ordonné des images-nombres. On identifie dans le tableau l'image affichée à coté d'une mine et on la remplace systématiquement par sa suivante dans le même tableau :

var Nombres = new Array(10);
Nombres[0] = "dessous.gif";
Nombres[1] = "un.gif";
Nombres[2] = "deux.gif";
Nombres[3] = "trois.gif";
Nombres[4] = "quatre.gif";
Nombres[5] = "cinq.gif";
Nombres[6] = "six.gif";
Nombres[7] = "sept.gif";
Nombres[8] = "huit.gif";
Nombres[9] = "neuf.gif";

function changeImage(laquelle) {
if (plateau[laquelle] != "bomb.gif" ) {       // c'est pas une mine

for ( UneImage = 0 ; UneImage < Nombres.length ; UneImage++) {
    if (plateau[laquelle] == Nombres[UneImage] )
      //   si c'est un "un" alors met un deux, si un deux, mets un trois...
     {
     plateau[laquelle] = Nombres[UneImage+1];
     break;
    }
  }
 }
}

 

Ça y est ! Le plateau virtuel est complet. On dispose dès lors d'un plateau "réel" en HTML qui montre 100 cases grises cliquables ("dessus.gif") et aussi d'un plateau vrtuel miroir du premier dans lequel on a d'abord placé 100 images neutres ("dessous.gif"), dont quelques unes furent ensuite remplacées de façon aléatoire, par des images de mines. On a placé des images chiffres, enfin, autour de ces mines. Le jeu n'attend plus que les clics de l'utilisateur.

 

 Gestion du clic utilisateur

Nous souhaitons que l'utilisateur ait le choix. Un clic "simple" dévoile une case, Alt/Option + clic marque la case d'un petit point d'interrogation ("doute.gif"). Nous devons être en mesure de détecter si la touche Alt/Option est enfoncée ou non lors du clic.

Une première chose : La fonction que nous avons besoin de rédiger doit distinguer les navigateurs de Netscape et de Microsoft.

En effet, N4 et IE4 gèrent les événements de façon différente. Dans Netscape, un objet Event est chaque fois passé en argument à tout gestionnaire d'événement.

Dans les gestionnaire intégré au code HTML par exemple "onClick=", on désigne l'objet Event actuellement transmis par le mot clé "event".

La propriété "modifiers" de cet objet prend une valeur différente selon que telle ou telle touche a accompagné ou non l'événement. Exemple : event.modifiers == 0 , aucune touche; event.modifiers == 1 pour Alt ; 2 pour Contrôle; 3 pour Contrôle + Alt; 4 pour Maj; sur le Mac, Commande équivaut à 8; Contrôle + Maj à 6 (4+2); Alt + maj à 5 (1 + 4); etc. Ces valeurs numériques peuvent changer selon les versions et les plates-formes. Aussi sont-elle représentées par les constantes Event.SHIFT_MASK, Event.ALT_MASK...

Dans IE4 maintenant, le type du plus récent événement est automatiquement enregistré dans l'unique propriété "event" de l'objet window. À ne pas confondre avec le mot clé "event" de Netscape qui désigne chaque fois l'objet-évènement en cours !

Dans IE4, l'expression "window.event.shiftKey" prendra la valeur true si l'événement s'est accompagné de la touche Maj.

À toute chose malheur est bon, nous utilisons cette différence dans le traitement des événements (dans les objets Event et event) pour distinguer les deux navigateurs :

 

var cestNetscape = window.Event ? true : false;  

 

S'il existe un objet Event : c'est Netscape puisque IE4 ne connaît que la propriété "event" 

Puis nous écrivons notre fonction de sorte qu'elle renvoie la valeur true si la touche Alt est enfoncée.

 

function testLeClavierQuand(cetEvenement) {
var cestNetscape = window.Event ? true : false;

if (cestNetscape) {
    AltEnfoncee = (cetEvenement.modifiers == Event.ALT_MASK);
    } else {
    AltEnfoncee = window.event.altKey ;
   }
return AltEnfoncee
}

 

Ce test doit être lancé au clic de l'utilisateur, appelant la fonction doIt(). Si la touche Alt est enfoncée, l'image HTML change simplement ("dessus.gif" devient "doute.gif" et vice-versa) :

 

function doIt(cetEvenement, rangee, colonne) {
var a = rangee*10+colonne;               // on récupère la position exacte de la case cliquée

if (testLeClavierQuand(cetEvenement)) {
   if (document.images[a].src.lastIndexOf("doute.gif") == - 1) {
   document.images[a].src = "doute.gif";
   } else {
  document.images[a].src = "dessus.gif";
  }

} else {
...

Et si... si la touche n'était pas enfoncée ? Alors on doit dévoiler la case... C'est-à-dire lui substituer son équivalent dans le plateau virtuel...

   document.images[a].src = plateau[a];

Et bien sûr, procéder à un test simple...

    if (plateau[a] == "bomb.gif") {alert ("Boum !");}

Mais surtout, si c'était une case neutre, on doit dévoiler AUSSI les cases environnantes !

   if (plateau[a] == "dessous.gif") {devoileAutourDe(a);}
  }
}

Dévoiler aussi les cases environnantes... Il faut donc les connaître. Justement, nous pouvons les connaître ! Notre fonction lesImagesAutourDe() va resservir à cette fin.

 

function devoileAutourDe(cetteCase) {
 for (n = 0 ; n < lesImagesAutourDe(cetteCase).length ; n++) {
  w = lesImagesAutourDe(cetteCase)[n];

       if (document.images[w].src.lastIndexOf("dessus.gif") != - 1) {
      document.images[w].src = plateau[w];
     }
  }
}

 

Voilà, le script est maintenant complet. Toutes ses fonctions imbriquées étant successivement appelées par la fonction qui crée le plateau de jeu, on veillera à ce qu'elle soient bien lues AVANT que ne soit effectivement appelée definitLePlateau(). Le navigateur charge par ordre de lecture. On finit le script par l'appel de la fonction principale :

    ...
  }
}

definitLePlateau();

</SCRIPT> </HEAD> </HTML>

Le document, enregistré à coté des images, avec une extension .HTML, se chargera comme une page Web dans le navigateur et affichera notre jeu.

 

 

 

Vous pouvez téléchargez les images et le code source du jeu au format ZIP en cliquant ici.

Dans un article ancien, l'on présentait les notions nécessaires pour débuter en JavaScript.

Netscape propose le téléchargement du manuel de référence de JavaScript sur son site : http://developper.netscape.com/docs/manuals/
communicator/jsguide4/index.htm