Septième et avant-dernière partie de ce making-of et qui signe… le retour du blabla sur le code ! Il faut en effet oublier mes deux autres billets consacrés au code (billet 3 et billet 4)… Ou plutôt : il ne faut pas oublier les propos que j’y tiens mais le code que j’y présente car… ô douloureuse surprise, j’ai découvert en cours de réalisation du projet qu’il ne fonctionnait pas en conditions réelles ! C’est mon absence éhontée de logique qui m’a perdu dans les conditions de passage au chapitre ou à la case suivante. Bref, le seul code valide est celui que je présente aujourd’hui, tel qu’il est utilisé actuellement par le site.
Outre partager l’intégralité du script principal qui gère le fonctionnement des Monstres d’Amphitrite, je voudrais aussi poursuivre l’approche pédagogique des précédents billets. Après avoir retracé les opérations mentales de mise en place du code, j’aimerais continuer de développer ma réponse à la question « je comprends rien en informatique, comment ça marche tes trucs ? » Toujours à mon niveau de petit bricoleur, je tente ci-dessous d’expliquer ce que je comprends de ce qu’est un programme informatique à tous ceux qui n’en ont pas la moindre idée ! Vous me direz si le pari est relevé…
Petit mode d’emploi de ce billet : j’introduis plus ou moins longuement chaque partie de mon script, puis le script lui-même est truffé de commentaires (en orange) qui le détaillent ligne par ligne.
C’est parti !
L’objectif principal du programme est d’afficher la bonne case au bon moment au bon endroit au fil des clics du lecteur, et le faire changer de chapitre s’il y a lieu. Il va également déclencher divers événements annexes telles que le clignotement des contours de case. Pour assurer ces missions, le programme doit en permanence savoir « où on en est », c’est-à-dire quelle case est affichée à l’instant t, quelle sera la suivante, etc. A chaque clic du lecteur, ces paramètres évoluent. Pour le programme, ce sont des valeurs qui varient dans le temps de l’exécution : des variables. En premier lieu, on initialise les variables principales du programme : certaines se voient attribuer une valeur de départ qui changera au cours de l’utilisation, certaine se voient attribuer une valeur qui ne changera pas, enfin certaines sont encore vides et ne se verront attribuer de valeurs qu’ultérieurement au cours de l’utilisation. Pour les Monstres d’Amphitrite, nous avons besoin des variables suivantes :
var count = 0,//compteur de cases: indique le numéro de la case en cours à l'instant t
//
//tableau des listes de cases indiquant leur ordre d'activation et d'affichage
successif pour chaque chapitre:
strList = [
['B A B A B A B A B C D'],
['B B A A A B D D B A B C A C D A B C D D D'],
['A A D B A B D C D B A A B D'],
//choix multiple chapitre 3, une liste pour chacune des deux branches possibles:
['B B A C D B A D A D A D A D B D B D B D B D B D B B B B A B B A A A A B A B A
A A A A A A B B B A C D',
'A A B B B D D D D C C C C A A A A B B B B B D D D D C C C C A A A A B B B B D
D D D C C C C A A A A B B B B D D D D C C C C A A A B B B D C'],
//
['A A C C C D D D A C A D C D D B B D D A A C C C D C C C B A C C D'],
['A A A C A C A C A C A C B D B D B D B B B B D B D B D B D B D']
],
//
order = [],//servira le moment venu au stockage provisoire en mémoire de la liste
de cases du chapitre en cours à l'instant t
img,//servira à stocker un objet image
choice = 0,//initialisation du nombre de choix de parcours possibles, par défaut zéro
timer;//sera utilisée pour les clignotements des cases
Le programme comporte ensuite un certain nombre de fonctions, c’est-à-dire plusieurs séries d’instructions qui vont être exécutées (ou « jouées », si on veut) à divers moments de la lecture, soit en fonction de la valeur de telle ou telle variable, soit en fonction d’une action du lecteur sur l’interface. Chaque fonction pourra être exécutée autant de fois que nécessaire durant l’utilisation.
Une première fonction va permettre d’adapter la taille des cases à la taille de la fenêtre du navigateur. Cette fonction va être déclenchée une fois au début de chaque chapitre, et également à chaque fois que le lecteur redimensionnera la fenêtre du navigateur. Rien de bien sorcier dans cette fonction : le programme récupère les dimensions de la fenêtre, puis il recalcule proportionnellement les dimensions des cases. Ce calcul se fait en deux temps et nécessite l’utilisation d’une condition : si la fenêtre est plus large que haute, les cases seront redimensionnées selon un premier calcul, si la fenêtre est plus haute que large, elles seront redimensionnées selon un second calcul. Comme les mots-clés des conditions sont en anglais dans ce langage (if…else), c’est plutôt facile à comprendre:
function redim() {
//
//récupération des dimensions de la fenêtre, moins les marges et menus:
var largeur = $(window).width();
var hauteur = $(window).height()-45-$('#footer').height();
//
//calculs des nouvelles dimensions des cases:
if (largeur >= hauteur) {//si la fenêtre est plus large que haute
var htrCase = hauteur*45/100;//nouvelle hauteur des cases = 45% de la hauteur
de la fenêtre
if (htrCase>=400) {
htrCase = 400;
}//hauteur maximale des cases limitée à 400px
var lrgCase = htrCase*500/400;//nouvelle largeur des cases proportionnelle à
la nouvelle hauteur
$('.strip img')
.css('height', htrCase)
.css('width', lrgCase);//application des nouvelles dimensions à l'image
} else {//ou (si la fenêtre est plus haute que large)
var lrgCase = largeur*45/100;//nouvelle largeur des cases = 45% de la largeur
de la fenêtre
if (lrgCase>=500) {
lrgCase = 500;
}//largeur maximale des cases limitées à 500px
var htrCase = lrgCase*400/500;//nouvelle hauteur des cases proportionnelle à
la nouvelle largeur
$('.strip img')
.css('height', htrCase)
.css('width', lrgCase);//application des nouvelles dimensions à l'image
}
}
//
$(window).resize(redim);//exécution de la fonction redim() lors des redimensionnements
de la fenêtre par l'utilisateur
La fonction qui suit n’est exécutée qu’une seule fois au début de chaque chapitre ; c’est pourquoi je l’ai appelée init(). Elle sert à assurer le bon affichage de la page et à définir certains paramètres propres au chapitre qui commence et que le programme a besoin de connaître (encore des variables !). Elle active enfin le clignotement de la première case cliquable et le préchargement de l’image suivante en exécutant ou appelant des fonctions que l’on va voir après.
function init() {
//
redim();//exécution de la fonction redim()
//
//définition du nombre de choix de parcours possibles pour les chapitres concernés:
if (seq==2) {//note: la variable seq indiquant le numéro du chapitre en cours est
définie dans un autre fichier
choice = 2;
}
//
//récupération de la liste de cases correspondant au chapitre en cours
parmi les listes établies auparavant:
order = strList[seq][folder].split(' ');//note: la variable folder indiquant le numéro
du sous-dossier contenant les images est définie dans un autre fichier
//
$('#'+order[count])//définition de la case active...
.css('cursor', 'pointer')//...des pointeurs de souris correspondants (la souris se
transforme en main quand on survole la case active)...
.on('click', next);//...et appel de la fonction next() permettant le passage à la
suite lors du clic
preload(count+1);//préchargement de la case suivante
//
// clignotement de la case active:
$('#'+order[count]).addClass('anim');//ajout d'un élément permettant au programme de
reconnaître quelle case est active et doit clignoter
wink(100);//appel de la fonction wink(): le paramètre entre parenthèses indique
qu'elle sera jouée 100 fois
//
}
//
init();//lancement du chapitre!
La fonction init() appelle notamment la fonction wink() qui assure le clignotement de la case actuellement cliquable. Il s’agit d’un changement de couleur progressif du contours qui est répété en boucle un certain nombre de fois afin de produire un effet de clignotement. Le nombre de fois que l’animation sera jouée est déterminée lors de l’appel à la fonction : j’ai choisi de la faire jouer 2 fois sauf pour la première case du chapitre ou en cas de choix multiples où elle est jouée 100 fois; afin de permettre au lecteur de se repérer en prendre ses marques, notamment quand la modalité d’interaction change soudainement de la convention fixée depuis le départ.
function wink(loop) {//le paramètre loop indique le nombre de fois que l'animation sera jouée
//
//définition d'une animation en boucle:
//
var j=0;
//une fonction dans la fonction précise les instructions pour le clignotement:
function anim() {
$('.anim')
//la couleur du contours varie 4 fois de manière progressive sur une durée
de 100 millisecondes:
.animate({borderColor:'#DDDDDD'}, 100)
.animate({borderColor:'#000'}, 100)
.animate({borderColor:'#DDDDDD'}, 100)
.animate({borderColor:'#000'}, 100);
j++;
if (j<loop) {
timer = setTimeout(anim, 1000);//la fonction anim() est rejouée toutes les
secondes jusqu'à avoir été répétée le nombre de fois prévu
}
}
anim();//la fonction anim() est déclenchée une première fois
}
La fonction centrale sur laquelle tout repose est celle qui donne les instructions à suivre au moment où le lecteur clique sur une image. Faut-il afficher l’image suivante ou faut-il changer de chapitre ? Quelle image faut-il afficher à cet instant ou à quel chapitre faut-il passer ? Pour le déterminer, cette fonction vérifie les valeurs des variables qu’on a initialisées en début de programme, puis en actualise les valeurs après avoir effectué son travail. Ainsi, lors du clic, les variables changent de valeur et le programme saura toujours « où on en est » lors du clic qui suivra, etc. Une première série d’instructions fait en sorte de rendre « inactive » la case sur laquelle le lecteur vient de cliquer : logique puisque c’est la case suivante qui doit prendre le relais. Une seconde série d’instructions vérifie et assure les passage à la case ou au chapitre suivant et rend active la case qui doit le devenir.
function next() {
//
// "désactivation" de la case sur laquelle le lecteur vient de cliquer:
$('.anim').removeClass();//retrait de l'élément permettant au programme d'identifier
la case cliquée comme "active"
clearTimeout(timer);//arrêt du clignotement de la case cliquée
$(this)
.css('cursor', 'auto')//réinitialisation du pointeur de la souris sur la case cliquée
.off('click', next);//désactivation du clic
count++;//ajout de 1 au compteur de case
//
//vérifications du remplissage des conditions requises pour passer à la case ou au
chapitre suivant et exécution des instructions en conséquence:
//
if (count>=order.length-choice) {//si le compteur de cases indique un numéro supérieur
ou égal au nombre total de cases du chapitre...
//
//...alors exécution des instructions de fin de chapitre:
seq++;//ajout de 1 au compteur de chapitres
//
if (choice>0) {//si le chapitre actuel se termine par un choix de parcours...
//
//...les x dernières cases affichées deviennent actives et clignotantes (x correspondant
au nombre de parcours possibles):
for (j=0, i=count; i<=order.length; i++) {//pour toutes les cases concernées...
$('#'+order[count]).addClass('anim');//...ajout d'un élément permettant au programme de
l'identifier comme "active"
var a = '<a href="index.php?s='+seq+'&f='+j+'"/>';//...préparation d'un hyperlien vers
la page correspondante...
$('#'+order[count])
.css('cursor', 'pointer')//...définition des pointeurs de souris...
.wrap(a);//...et tranformation de la case en hyperlien
count++;//ajout de 1 au compteur de cases
if(j<choice) {
j++;//répétition de ces instructions pour toutes les cases concernées
}
}
wink(100);//exécution de la fonction wink() pour 100 clignotements pour toutes les
cases concernées
//
} else {//sinon (= si le chapitre ne se finit pas par un choix de parcours)...
//
//...alors accès direct au chapitre suivant:
location.href = 'index.php?s='+seq+'&f=0';
}
//
} else {//sinon (= si le compteur de case n'indique pas un nombre supérieur ou égal au
nombre total de cases du chapitre)...
//
//...alors exécution des instructions d'affichage de la case suivante:
//
if (count>=order.length-choice-1 && seq>=strList.length-1) {//Si le compteur de cases
indique un nombre supérieur ou égal au nombre de cases du chapitre et si le compeur
de chapitres indique un nombre supérieur ou égal au nombre total de chapitres...
//
//...alors exécution des instructions de fin de l'histoire:
$('#'+order[count]).attr('src', img.src);//affichage de la dernière image
//
} else {//...sinon...
//
//...affichage et activation de la case suivante:
$('#'+order[count]).addClass('anim');//ajout d'un élément permettant au programme
d'identifier la case comme "active"
wink(2);//exécution de la fonction wink() pour 2 clignotements
$('#'+order[count])
.attr('src', img.src)//affichage de l'image suivante
.css('cursor', 'pointer')//définition des pointeurs de souris
.on('click', next);//appel à la fonction next() lors du clic sur la case active
preload(count+1);//préchargement de l'image qui viendra après
}
}
}
Il ne reste plus qu’une toute petite fonction : celle qui sert à précharger les images et qui a déjà été utilisée à plusieurs reprises :
function preload(i) {//le paramètre i indique le numéro de l'image à précharger
img = new Image();//création d'un objet image
img.src = 'images/SEQ'+seq+'/'+folder+'/'+i+'.png';//chargement de l'image dans l'objet image;
le navigateur considère alors qu'il y a une image à charger dans le cache même si elle n'est
pas immédiatement affichée à l'écran
}
Si vous n’avez pas compris un traître mot de ce que je raconte, j’ai raté mon pari… Ne renoncez pas pour autant à la lecture du prochain billet: quelques remarques en guise de conclusion.