Exercice pratique : le MASTERMIND
Bonjour,
Aujourd'hui je m'attaque au Mastermind.
Tout d'abord le résultat :
Les sources sont disponibles en fin d'exercice.
Etude préliminaire
Tout d'abord le MASTERMIND c'est quoi ? (merci Wikipedia)
Le Mastermind est un jeu de société, de réflexion, et de déduction, inventé par Mordecai Meirowitz dans les années 1970. Il se présente généralement sous la forme d'un plateau perforé de 10 rangées de quatre trous pouvant accueillir des pions de couleurs. Le nombre de pions de couleurs différentes est de 8 et les huit couleurs sont généralement : rouge ; jaune ; vert ; bleu ; orange ; blanc ; violet ; fuchsia. Il y a également des pions blancs et rouges (ou noirs) utilisés pour donner des indications à chaque étape du jeu. Il existe de nombreuses variantes suivant le nombre de couleurs, de rangées ou de trous.
Un joueur commence par placer son choix de pions sans qu'ils soient vus de l'autre joueur à l'arrière d'un cache qui les masquera à la vue de celui-ci jusqu'à la fin de la manche. Le joueur qui n'a pas sélectionné les pions doit trouver quels sont les quatre pions, c'est-à-dire leurs couleurs et positions. Pour cela, à chaque tour, le joueur doit se servir de pions pour remplir une rangée selon l'idée qu'il se fait des pions dissimulés.
Une fois les pions placés, l'autre joueur indique :
- le nombre de pions de la bonne couleur bien placés en utilisant le même nombre de pions rouges ;
- le nombre de pions de la bonne couleur, mais mal placés, avec les pions blancs.
Il arrive donc surtout en début de partie qu'il ne fasse rien concrètement et qu'il n'ait à dire qu'aucun pion ne correspond, en couleur ou en couleur et position.
La tactique du joueur actif consiste à sélectionner en fonction des coups précédents, couleurs et positions, de manière à obtenir le maximum d'informations de la réponse du partenaire puisque le nombre de propositions est limité par le nombre de rangées de trous du jeu. Dans la plupart des cas, il s'efforce de se rapprocher le plus possible de la solution, compte-tenu des réponses précédentes, mais il peut aussi former une combinaison dans le seul but de vérifier une partie des conclusions des coups précédents et de faire en conséquence la proposition la plus propice à la déduction d'une nouvelle information.
Le joueur gagne cette manche s'il donne la bonne combinaison de pions sur la dernière rangée ou avant.
Dans notre cas nous allons utiliser une variante à 5 pions et 5 couleurs et c'est l'ordinateur qui choisira automatiquement la solution. Etant donné que j'ai toujours eu du mal avec les pions blancs et noirs pour indiquer au joueur la validité de ses combinaisons, je vais les remplacer par du texte, mais le résultat est le même.
Les pré-requis
Je vous recommande fortement d'avoir au moins lu les exercices suivants : DEMINEUR, PONG, SNAKE, TAQUIN
Les exercices sont courts mais de nombreuses astuces y sont proposées, je ne les expliquerai pas à chaque nouvel exercice.
Pour ce programme vous devez connaître :
Variables et types : http://help.adobe.com/fr_FR/FlashPlatform/reference/actionscript/3/statements.html#var
Fonctions et paramètres : http://help.adobe.com/fr_FR/FlashPlatform/reference/actionscript/3/statements.html#function
Manipulation de tableaux : http://help.adobe.com/fr_FR/FlashPlatform/reference/actionscript/3/Array.html
Ecouteurs d'événements : http://help.adobe.com/fr_FR/FlashPlatform/reference/actionscript/3/flash/events/package-detail.html
Exercice DEMINEUR : http://forums.mediabox.fr/wiki/tutoriaux/flashplatform/jeux/exercice_-_demineur
Exercice PONG : http://forums.mediabox.fr/wiki/tutoriaux/flashplatform/jeux/exercice_-_le_pong
Exercice TAQUIN : http://forums.mediabox.fr/wiki/tutoriaux/flashplatform/jeux/exercice_-_le_taquin
Exercice SNAKE : http://forums.mediabox.fr/wiki/tutoriaux/flashplatform/jeux/exercice_le_snake
Si vous souhaitez plus de précisions sur ces points, je vous encourage à parcourir le Wiki de Mediabox où vous trouverez de nombreux tutoriaux détaillés.
Le code
Voyons d'abord tout le code d'un coup.
const N:int = 5; // clous const C:int = 5; // couleurs const E:int = 10; // essais const T:int = 30; // positions var i:int; var tours:int; var ligne:Array; var solution:Array = [0,0,0,0,0]; var couleurs:Array = [0,0,0,0,0]; var texte:TextField; var bouton:Bouton = new Bouton(); var plateau:Plateau = new Plateau(); bouton.x = 208; bouton.buttonMode = true; bouton.addEventListener(MouseEvent.CLICK,valider); addChild(bouton); addChild(plateau); // interface var panneaux:Panneaux = new Panneaux(); panneaux.addEventListener(MouseEvent.MOUSE_DOWN, init); panneaux.buttonMode = true; addChild(panneaux); // initialisation function init(e:Event):void{ while(numChildren>3) removeChildAt(3); tours = 0; for (i=0; i<N; i++) solution[i] = int(Math.random()*C)+1; creeLigne(); } // créer une ligne function creeLigne():void{ ligne = []; for (i=0; i<N; i++) ajoutePion(i, 1); bouton.y = tours*T+T; ajouteTexte("Placez les clous et validez !"); } // ajouter un pion function ajoutePion(j:int, f:int):void{ var p:Pions = new Pions(); p.x = j*T+T; p.y = tours*T+T; p.couleur = 0; p.gotoAndStop(f); p.addEventListener(MouseEvent.CLICK,posePions); p.buttonMode = true; addChild(p); ligne.push(p); } // ajouter du texte function ajouteTexte(t:String){ texte = new TextField(); texte.x = 290; texte.y = tours*T+T; texte.width = 300; texte.text = t; texte.textColor = 0xFFFFFF; addChild(texte); } // poser un pion function posePions(e:MouseEvent){ var p:Object = e.currentTarget; if (p.couleur < C) p.couleur++ else p.couleur = 0; p.gotoAndStop(p.couleur+1); } // valider function valider(e:MouseEvent){ var p:int = 0; var c:int = 0; var sol:Array = [0,0,0,0,0]; var cou:Array = [0,0,0,0,0]; var t:Object; for (i=0; i<N; i++) { t = ligne[i]; if (t.couleur == solution[i]) { p++; }else{ sol[solution[i]-1]++; cou[t.couleur-1]++; } t.removeEventListener(MouseEvent.CLICK,posePions); t.buttonMode = false; } for (i=0; i<C; i++) c += Math.min(sol[i],cou[i]); texte.text = "Emplacements : " + p + " - Couleurs : " + c; texte.textColor = 0xFFFFFF; tours++; if (p == N){ finPartie(true); }else{ if (tours == E) finPartie(false) else creeLigne(); } } // fin de partie function finPartie(w:Boolean){ bouton.y = tours*T+T; if (w) { ajouteTexte("Gagné !"); panneaux.gotoAndStop(3); } else { ajouteTexte("Perdu !"); panneaux.gotoAndStop(2); } for (i=0; i<N; i++) ajoutePion(i, solution[i]+1); addChild(panneaux); }
Etude du programme
Comme d'habitude j'utilise la bibliothèque de Flash pour gérer les graphismes, si vous n'utilisez pas Flash voici les modifications à faire dans le programme :
Panneaux :
- un clip qui regroupe tous les panneaux d'interface
- un panneau différent par frame
Pions :
- un clip qui regroupe toutes les couleurs des pions
- une couleur sur chaque frame
- le première frames représente un emplacement
Bouton :
- le bouton de validation
Plateau :
- le plateau de jeu (décor)
Allez c'est parti pour l'étude pas à pas :
const N:int = 5; // clous const C:int = 5; // couleurs const E:int = 10; // essais const T:int = 30; // positions var i:int; var tours:int; var ligne:Array; var solution:Array = [0,0,0,0,0]; var couleurs:Array = [0,0,0,0,0]; var texte:TextField; var bouton:Bouton = new Bouton(); var plateau:Plateau = new Plateau(); bouton.x = 208; bouton.buttonMode = true; bouton.addEventListener(MouseEvent.CLICK,valider); addChild(bouton); addChild(plateau);
On se débarrasse tout de suite des déclarations globales, tout ceci est connu si vous avez fait les exercices précédents, nous avons les constantes (invariables), les pointeurs pour les boucles, les tableaux et les objets.
// interface var panneaux:Panneaux = new Panneaux(); panneaux.addEventListener(MouseEvent.MOUSE_DOWN, init); panneaux.buttonMode = true; addChild(panneaux);
J'ajoute le panneau d'accueil du jeu, puis je lance l'initialisation.
// initialisation function init(e:Event):void{ while(numChildren>3) removeChildAt(3); tours = 0; for (i=0; i<N; i++) solution[i] = int(Math.random()*C)+1; creeLigne(); }
Je supprime tous les objets à part les trois premiers de la liste d'affichage, je remet le nombre de tours à zéro, je tire une solution aléatoire, et je crée la première ligne de pions.
// créer une ligne function creeLigne():void{ ligne = []; for (i=0; i<N; i++) ajoutePion(i, 1); bouton.y = tours*T+T; ajouteTexte("Placez les clous et validez !"); }
A chaque fois que je veux créer une nouvelle ligne, je vide le tableau de la ligne en cours, j'ajoute 5 pions (on vois ça juste après), je place le bouton de validation en face de la ligne nouvellement créée, et j'ajoute le texte de départ (on vois ça aussi après).
// ajouter un pion function ajoutePion(j:int, f:int):void{ var p:Pions = new Pions(); p.x = j*T+T; p.y = tours*T+T; p.couleur = 0; p.gotoAndStop(f); p.addEventListener(MouseEvent.CLICK,posePions); p.buttonMode = true; addChild(p); ligne.push(p); }
A chaque fois que j'ajoute un nouveau pion, je le crée, je le positionne en X et en Y en fonction de la place dans la ligne et de la position de la ligne sur Y, je lui donne une couleur par défaut de zéro, donc vide, je lui donne un écouteur qui va permettre de changer sa couleur, je l'ajoute à l'affichage et je l'enregistre dans la ligne nouvellement créée.
// ajouter du texte function ajouteTexte(t:String){ texte = new TextField(); texte.x = 290; texte.y = tours*T+T; texte.width = 300; texte.text = t; texte.textColor = 0xFFFFFF; addChild(texte); }
Lorsque je veux ajouter du texte, je crée un nouveau textField, je le place sur X (toujours à la même position), je le place sur Y en fonction de la position de la ligne qu'on est en train de jouer, je lui donne sa taille, sa couleur et son contenu (à vous d'adapter au besoin), puis je l'ajoute à la liste d'affichage.
// poser un pion function posePions(e:MouseEvent){ var p:Object = e.currentTarget; if (p.couleur < C) p.couleur++ else p.couleur = 0; p.gotoAndStop(p.couleur+1); }
Lorsque le joueur veut changer la couleur d'un pion, donc lorsqu'il cliques dessus, j'avance d'une frame dans le clip du pion, ce qui change sa couleur. Si je dépasse le nombre de couleurs autorisé je reviens au début du clip, donc à un emplacement vide. Notez que la couleur est enregistrée dans un paramètre du clip nommé “couleur”, cela servira plus tard pour éviter d'aller regarder le numéro de la frame affichée par le clip.
// valider function valider(e:MouseEvent){ var p:int = 0; var c:int = 0; var sol:Array = [0,0,0,0,0]; var cou:Array = [0,0,0,0,0]; var t:Object; for (i=0; i<N; i++) { t = ligne[i]; if (t.couleur == solution[i]) { p++; }else{ sol[solution[i]-1]++; cou[t.couleur-1]++; } t.removeEventListener(MouseEvent.CLICK,posePions); t.buttonMode = false; } for (i=0; i<C; i++) c += Math.min(sol[i],cou[i]); texte.text = "Emplacements : " + p + " - Couleurs : " + c; texte.textColor = 0xFFFFFF; tours++; if (p == N){ finPartie(true); }else{ if (tours == E) finPartie(false) else creeLigne(); } }
On attaque la plus grosse partie, lorsque le joueur clique sur le bouton de validation, le programme vérifie les combinaisons, pour cela je boucle sur les pions contenus dans la ligne en cours, pour chacun je regarde si la couleur correspond à la couleur enregistrée dans la solution, pour chaque couleur valide et bien placée j'incrémente un petit compteur. Si le pion testé ne correspond pas à la solution, je dois savoir si il est de la bonne couleur mais mal placé, pour cela j'enregistre sa position et sa couleur dans deux petits tableaux temporaires (il y a un décalage de 1 car mon décompte de couleurs commence à 1 et mes tableaux à 0). Je retire l'écouteur sur le pion afin que le joueur ne puisse plus le modifier.
Je fais ensuite une boucle sur le nombre total de couleurs du jeu, ce pointeur va me donner le nombre de pions mal placés mais de la bonne couleur, pour cela je regarde la valeur minimum entre le placement et la couleur, un pion mal placé aura un placement de 0 et si il a la bonne couleur il aura une couleur de 1.
J'affiche ensuite le texte d'information donnant le nombre de pions bien placés ayant la bonne couleur, et le nombre de pions mal placés mais avec la bonne couleur.
Enfin, j'incrémente le nombre de tours, puis je vérifie si le joueur à gagné (tous les pions de la bonne couleur bien placés), si ce n'est pas le cas, si le joueur à atteint le nombre de tours maximum il perd la partie, sinon je crée une nouvelle ligne pour le laisser continuer à chercher.
// fin de partie function finPartie(w:Boolean){ bouton.y = tours*T+T; if (w) { ajouteTexte("Gagné !"); panneaux.gotoAndStop(3); } else { ajouteTexte("Perdu !"); panneaux.gotoAndStop(2); } for (i=0; i<N; i++) ajoutePion(i, solution[i]+1); addChild(panneaux); }
A la fin de la partie, je place le bouton de validation sur la dernière ligne, selon si le joueur à gagné ou perdu j'affiche le texte et le panneau correspondant, puis j'affiche la solution sur la dernière ligne, et pour terminer j'affiche le panneau de fin.
Conclusion
Mastermind est un jeu très simple que vous pouvez décliner sous de nombreuses formes, je n'ai pas grand chose à dire en conclusion, techniquement le jeu n'est pas dur, je joue cependant avec les textes afin de vous entraîner à les manipuler comme tous les autres objets.
Les sources
