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 manuvre 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
|