Admin/Dev

23
Janv.
2019

Développer un MasterMind en Javascript - Partie 4

Publié par sky

Lors du dernier épisode, nous nous étions arrêté à la mise en place du plateau de jeu. Nous devons désormais créer l'interaction avec le joueur afin de lui permettre de jouer.

Lorsque nous avions écrit la fonction qui dessine le plateau, nous avions appliquer, à l'événement "click" des différentes couleurs du sélecteur, l'exécution d'un fonction nommée selectColor.

C'est donc cette fonction que nous devons écrire. Mais avant, réfléchissons à ce qu'elle doit faire.

En premier lieu, elle doit avoir en paramètre la valeur de la couleur à ajouter. C'est évidement son identifiant qu'il faut envoyer, et pas les composantes rouge, verte et bleue au format hexadécimal, qui définissent la couleur. Si l'on reprend les valeurs de notre variable colors, nous devons, par exemple, récupérer la valeur 4 et pas la valeur "#ff9600".

La première chose à faire est de vérifier que le jeu est bien en cours. En effet, si la partie est terminée, il ne faut pas que le joueur puisse continuer à placer des couleurs sinon, ce serait le bazar complet.

Nous continuerons en retirant la précédente sélection de la case s'il y en avait une.

Ensuite, nous devons d'une part enregistrer dans les variables, ce que le joueur à jouer, et d'autre part, l'afficher visuellement.

Enfin, pour permettre au joueur de continuer à jouer, nous allons déplacer le curseur sur la case suivante, et l'indiquer visuellement aussi. Petite subtilité, si le curseur était en bout de ligne, nous le remettrons au début. Il ne faudra pas oublier de retirer le marquage visuelle de la précédente case.

Voici la fonction, qui reproduit ce que nous venons de dire, avec un commentaire pour indiquer quelle action fait quoi.

selectColor: function(color) {
/* Verifie si la partie est toujours active */ if (this.game['turn'] == -1) {
return;
} /* Retire la precedente selection si elle existe */ document.getElementById('turn-'+this.game['turn']+'-'+this.game['column']).innerHTML = ''; /* Ajoute la couleur a la selection faite par le joueur */ this.game['selection'][this.game['column']] = color; /* Ajoute visuellement la couleur sur le plateau */ pion = document.createElement('div'); pion.className = 'pion'; pion.style.background = this.colors[color]; document.getElementById('turn-'+this.game['turn']+'-'+this.game['column']).appendChild(pion); /* Retire le marquage visuel de la case courante */ document.getElementById('turn-'+this.game['turn']+'-'+this.game['column']).className = ''; /* Verifie que le curseur n est pas sur la derniere case */ if (this.game['column'] == this.settings['columns']) {
/* Place le curseur a la premiere case */ this.game['column'] = 1;
} else {
/* Deplace le curseur du joueur sur la case suivante */ this.game['column'] ++;
} /* Ajoute le marquage visuel sur la nouvelle case courante */ document.getElementById('turn-'+this.game['turn']+'-'+this.game['column']).className = 'selected';
},

Vous pouvez recharger la page de votre jeu, et tester en cliquant sur les couleurs du sélecteur. N'hésitez pas à vérifier que le déplacement est bien cyclique, le curseur doit revenir au début de la ligne, lors que vous jouez la couleur sur la dernière case.

Comme à chaque étape, n'hésitez pas à vérifier que vous n'avez pas d'erreurs dans la console Javascript.

Avancer le curseur automatiquement quand le joueur joue, c'est bien. Mais, que se passe-t-il quand le joueur s'est trompé et qu'il souhaite changer sa sélection ?

Actuellement, il est obliger de faire le tour en espérant ne pas s'être de nouveau trompé, sinon, il gagne un nouveau tour gratuit, et ainsi de suite, jusqu'à n'en plus finir.

Ergonomiquement, ce n'est pas terrible. Pour palier à ce problème, nous allons permettre au joueur de sélectionner la case sur laquelle il veut jouer.

Si vous avez un peu étudier la méthode d'écriture du plateau de jeu, vous avez peut être remarqué qu'il existe une autre action placée sur l'événement "click" des cases du plateau. Il ne reste plus qu'à écrire cette méthode nommée selectColumn.

Ici nous avons 2 possibilités, nous pouvons soit indiquer la ligne et la colonne de la case cliquée en paramètre, vérifier qu'on se trouve bien sur la bonne ligne avant de sélectionner la nouvelle colonne. Soit nous pouvons limiter l'information à la colonne seulement, et partir du principe, que peut importe la ligne ou est cliquée la case, nous allons sur la colonne indiquée.

La seconde solution permet plus de souplesse, mais ergonomiquement, cela peut faire bizarre de cliquer sur une ligne, et que cela aille sur un autre. J'ai donc choisi appliquer la première solution, vous pouvez cependant adapter le code de la méthode et l'appel de la fonction dans le code HTML pour utiliser la seconde solution, si vous le préférez.

Notre méthode est toute simple, reprenant ce que l'on a déjà vu dans la méthode selectColor, en se limitant au changement de colonne, dans les variables, mais aussi visuellement.

Bien sur, il faudra vérifier que la partie est toujours en cours, mais comme nous devrons vérifier que nous sommes sur la bonne ligne, nous ferons d'une pierre, deux coups.

Voici la fonction, détaillée.

selectColumn: function(line, column) {
/* Verifie si la ligne est bien la ligne courante, verifie en meme temps, si la partie est toujours active */ if (line != this.game['turn']) {
return;
} /* Retire le marquage visuel de la case courante */ document.getElementById('turn-'+line+'-'+this.game['column']).className = ''; /* Selectionne la nouvelle colonne */ this.game['column'] = column; /* Applique le marquage visuel sur la nouvelle case courante */ document.getElementById('turn-'+line+'-'+this.game['column']).className = 'selected';
},

Vous pouvez lancer un nouveau test, vous verrez qu'ergonomiquement, nous y avons gagné. Qu'en pensez vous ?

Maintenant que le joueur est capable de choisir ses points avec aise, nous devons lui permettre de valider sa sélection. Pour cela, nous devons écrire la méthode "checkLine" qui s'exécute lors d'un clic sur le bouton OK se trouvant en bout de ligne.

Nous attaquons ici la partie la plus complexe du jeu, puisqu'il s'agit de la fonction qui doit déterminer si joueur a gagné, doit continuer à jouer ou a perdu.

Cette méthode devrait effectuer plein d'actions, et dans le bon ordre ! Comme d'habitude, avant de nous lancer, dans l'écriture sauvage du code, réfléchissons à ce que doit faire la méthode.

Comme d'habitude, nous commençons par vérifier que l'utilisation de la fonction est légitime, en regardant si la partie est toujours en cours. Mais comme pour la fonction selectColumn, nous allons aussi vérifier que la ligne indiquée en paramètre peut légitimement être jouée.

Cela peut paraître bête mais nous allons aussi vérifier que le joueur a bien placer un pion de couleur dans chacune des cases. En effet, le joueur a pu ne pas remplir sa ligne, et nous devons nous en assurer.

Ensuite nous aurons toute une grande partie dédiée à la vérification de la solution, de l'affichage des indications sur les couleurs bien ou mal placées. Selon le résultat de la vérification, il faudra afficher la victoire, la défaite ou aiguiller le joueur sur une nouvelle ligne.

Vérifier que le joueur a gagné, ou perdu est une tâche assez facile à coder, il n'en va pas de même pour le calcul des pions bien ou mal placés. La petite difficulté vient du fait que si plusieurs pions de couleurs identiques sont joués ou présents dans la solution, il faut limiter la vérification au maximum de pions présents.

Hum, pas si simple à expliquer ! Voyons avec un exemple. Si la solution contient 1 pion rouge et le joueur en joue deux, il faudra d'abord vérifier que l'un des deux est bien placé pour l'indiquer, si ce n'est pas le cas, indiquer le premier rouge joué est mal placé. Mais surtout, il ne faudra rien indiqué pour l'autre (qu'il s'agisse du premier ou deuxième pion). Afin de pouvoir faire ces tests, sans altérer la solution, nous la dupliquerons.

Pour effectuer ces vérifications nous allons faire de nombreux tests, et nous allons faire ces vérifications sur les variables que nous enregistrons précieusement au fur et à mesure. En aucun cas, nous ne ferons de tests sur l'affichage, cela serait plus compliqué, et sous-optimisé.

Ces tests vont nécessiter de parcourir la solution, ainsi que la proposition, cela se fera avec de simples boucles for. Nous les utiliserons aussi pour indiquer les résultats.

Pour afficher les résultats, nous aurons besoin de créer les classes de styles CSS dédiées, à ajouter dans le fichier MasterMind.css

.correct {display:inline-block; width:10px; height:10px; border-radius:5px; border:solid 1px #888; background:black;}
.misplaced {display:inline-block; width:10px; height:10px; border-radius:5px; border:solid 1px #888;}

Je crois que nous avons fais le tour, nous sommes prêts à attaquer l'écriture de la méthode, qui, plus que jamais, nécessitera quelques commentaires explicatifs.

checkLine: function(line) {
/* Verifie si la ligne est bien la ligne courante, verifie en meme temps, si la partie est toujours active */ if (line != this.game['turn']) {
return;
} /* Verifie que la ligne a ete entierement remplie par le joueur */ for (i = 1; i <= this.settings['columns']; i++) {
if (!this.game['selection'][i]) {
return;
}
} /* Duplique la solution pour pouvoir la modifier sans alterer l originale */ soluce = this.game['soluce'].slice(0); /* Initialise les variables de verification */ correct = 0; misplaced = 0; /* Verifie les pions bien places */ for (i = 1; i <= this.settings['columns']; i++) {
if (this.game['selection'][i] == soluce[i]) {
correct++; soluce[i] = 0; this.game['selection'][i] = 0;
}
} /* Verifie si tous les pions sont biens places, et auquel cas, afficher la victoire */ if (correct == this.settings['columns']) {
/* Utilise un return pour sortir de la method et ne pas continuer la verification */ return this.displayWin();
} /* Verifie les pions mal places, parmi les pions restant */ for (i = 1; i <= this.settings['columns']; i++) {
if (this.game['selection'][i] == 0) {
continue;
} loc = soluce.indexOf(this.game['selection'][i]); if (loc != -1) {
this.game['selection'][i] = 0; soluce[loc] = 0; misplaced++;
}
} /* Affiche le bon nombre de pions bien places */ for (i = 1; i <= correct; i++) {
pion = document.createElement('div'); pion.className = 'correct'; document.getElementById('result-'+this.game['turn']+'-'+i).appendChild(pion);
} /* Affiche le bon nombre de pions mal places */ for (j = i; j < i+misplaced; j++) {
pion = document.createElement('div'); pion.className = 'misplaced'; document.getElementById('result-'+this.game['turn']+'-'+j).appendChild(pion);
} /* Prepare le jeu pour le tour suivant */ /* Re-initialise la selection du joueur */ this.game['selection'] = new Array(); /* Retire le marquage visuel de la ligne courante  */ document.getElementById('turn-'+this.game['turn']).className = ''; /* Verifie que la ligne n etait pas la derniere, si auquel cas, afficher la defaite */ if (this.game['turn'] == this.settings['lines']) {
/* Utilise un return pour sortir de la method et ne pas continuer la verification */ return this.displayLose();
} /* Deplace le curseur sur la ligne suivante */ this.game['turn'] ++; /* Applique le marquage sur la nouvelle ligne courante */ document.getElementById('turn-'+this.game['turn']).className = 'selected'; /* Place le curseur sur la premiere case */ this.game['column'] = 1; /* Applique le marquage sur la premiere case */ document.getElementById('turn-'+this.game['turn']+'-1').className = 'selected';
},

Hop, une nouvelle petite phase de test pour vérifier que tout fonctionne.

Avez vous remarqué que le bouton OK ne s'affiche que sur la ligne courante ? Encore une petite optimisation, qui permet d'améliorer l'ergonomie. L'affichage et le masquage des boutons OK se fait en même temps que la selection de la ligne en cours, simplement grâce aux classes de style CSS.

Si vous allez au bout de votre test, vous verrez qu'il y aura 2 erreurs, l'une quand on gagne, et la seconde quand on perd. C'est tout à faire normal, nous appelons dans notre code 2 fonctions displayWin, et displayLose que nous n'avons pas encore écrites.

Dans le code HTML, nous avions prévu un espace nommé "result", nous allons nous en servir pour afficher les résultats. Dans le cas d'une victoire nous appliquerons un style différent à la ligne gagnante.

Nous allons afficher le résultat à droite du plateau, de manière fort visible.

Pour cela nous aurons besoin des classes de style CSS suivantes, à insérer dans notre fichier MasterMind.css

table tr.win td {background:#43b456;}

.page {font-size:0;}
.page .game {width:50%; display:inline-block; vertical-align:top;}
.page .options {width:50%; display:inline-block; vertical-align:top;}
.result {text-align:center; font-size:30px;}

Les fonctions sont, ensuite, assez simple à écrire.

displayWin: function() {
/* Affiche le resultat dans l espace dedie, en couleur */ document.getElementById('result').innerHTML = 'Gagné'; document.getElementById('result').style.color = '#43b456'; /* Affiche le marquage specific a la victoire sur la ligne courante */ document.getElementById('turn-'+this.game['turn']).className = 'win'; /* Marque la fin de la partie en indiquant une valeur null au tour en cours */ this.game['turn'] = -1;
}, displayLose: function() {
/* Affiche le resultat dans l espace dedie, en couleur */ document.getElementById('result').innerHTML = 'Perdu'; document.getElementById('result').style.color = '#CC3333'; /* Marque la fin de la partie en indiquant une valeur nulle au tour en cours */ this.game['turn'] = -1;
},

Et voila, la première version de notre jeu est complète, nous pouvons effectuer une partie de A à Z grâce aux quelques lignes de code de notre jeu. Bien sûr, nous pouvons l'améliorer, aussi bien graphiquement que techniquement. Pour le côté graphique, je vous laisse faire vos designs personnalisés. D'un point de vue technique, nous verrons comment nous pouvons ajouter de nouvelles fonctionnalités dans le dernier article dédié à ce MasterMind, qui sera disponible dans quelques jours.

A noter aussi, que le code a été écrit tel qu'il est venu, il pourrait largement être amélioré pour avoir quelque chose de plus propre, même si je trouve que le résultat est assez correct. Par exemple, nous utilisons styles et classes de styles sans uniformité, il serait préférable, pour faire du code propre, de ne se limiter qu'à l'usage des classes.

Pour ceux qui auraient des soucis avec le développement du jeu, les commentaires sont disponibles pour en discuter, et je mets à disposition les sources du jeu en l'état actuel.

 
Sommaire de la série
 
 
Commentaires
Aucun commentaire pour le moment.

 

Poster un commentaire
En postant sur skymac.org, je m'engage à être courtois et à ce que mon message soit pertinent avec le sujet de l'article.
En outre, j'accepte, sans condition, que mon message soit refusé et supprimé si ces règles ne sont pas appliquées.
Les cookies assurent le bon fonctionnement de nos services. En continuant, vous acceptez leur utilisation sur notre site internet.
Accepter