ISN - Organiser son mini-projet - exemple du Tic-Tac

Transcription

ISN - Organiser son mini-projet - exemple du Tic-Tac
ISN - Organiser son mini-projet - exemple du Tic-Tac-Toe
Ou « réfléchir avant de coder comme des brutes »
J’ai choisi cet exemple parce qu’on m’a posé la question, mais ce qui suit s’adapte à de nombreux mini-projets.
L’erreur classique du débutant est de se lancer dans le code et le mettre à jour progressivement, en améliorant par
touches, en implémentant de nouvelles fonctionnalités... Ça peut fonctionner. Certes. Mais le code risque d’être
monolithique, peu compréhensible, truffé de magouilles, et incompréhensible pour l’équipier, etc.
Et surtout, vous risquez d’aller dans le mur, boum.
Moi, je ne veux pas aller dans le mur, mais je suis perdu, comment je fais ?
⊲ Je réfléchis avant de poser la moindre ligne de code !
Exemple du Tic-Tac-Toe
On imagine assez bien une grille de Tic-Tac-Toe, qui n’y a jamais joué ?
⊲ A quoi ça peut ressembler en mode console ?
Avec les pipes | obtenus chez moi avec la combinaison de touches Alt Gr 6, à ça −→
|x| |o|
|o|x| |
|x|o|x|
⊲ Ok, mais comment joue-t-on, concrètement ?
• L’utilisateur du programme, que nous appellerons joueur, doit indiquer où il met une croix ou un cercle,
par les coordonnées ligne et colonne.
• Le programme vérifie que la saisie correspond bien à la zone de jeu (il n’y a que trois lignes et trois colonnes
→ saisie blindée).
• Le programme doit vérifier que la case est libre.
• Le programme pose alors la croix ou le cercle.
• Le programme affiche la grille complétée.
• Le programme vérifie si le coup est gagnant, et si oui, indique le joueur gagnant, fin de partie.
• Le programme vérifie si la partie est terminée sans gagnant et si c’est le cas l’indique, fin de partie.
• Si aucune de ces deux dernières conditions n’est respectée, c’est à l’autre joueur de jouer, donc on est dans
une boucle.
Écrit comme ça, je suis sûr que ça parait limpide.
Et bien chacune de ces fonctionnalités bien séparées du programme doit faire l’objet d’une fonction
à l’extérieur de notre fonction principale main ! Voir squelette de code.
⊲ Ok, mais comment je gère la mémoire des coups ? Où je les stocke ?
La grosse question. Ici il faudrait neuf variables, une par case. Je préconise le type int.
• Les variables sont initialisées à zéro, qui signifie « ni croix, ni cercle ».
• Si on pose une croix, on affecte la valeur 1 à la variable.
• Si on pose un cercle, on affecte la valeur 2 à la variable.
Trois choix possibles pour la structure :
• Neuf variables HautGauche, HautMilieu, HautDroite, MilieuGauche, etc, mais c’est lourd.
• Un tableau à neuf cases, numérotées de 0 à 8, déclaré ainsi : int cases[] = new int[9];
• Un tableau à deux dimensions, déclaré ainsi : int cases[][] = new int[3][3];
Ça peut effrayer, mais c’est ce qu’il y a de plus lisible et de plus facile pour mettre en place une procédure
d’affichage avec deux boucles for imbriquées.
La case en haut au milieu est cases[0][1] et celle en bas à droite cases[2][2].
Je préconise le troisième choix. Une procédure d’affichage peut alors s’écrire facilement ainsi :
for (i=0; i<3; i++){
for (j=0; j<3; j++)
print("|" + afficheCase(cases[i][j]));
println("|");
}
Où afficheCase est une fonction de type String qui retourne un « x » , un espace ou un « o » suivant la valeur
de l’entier envoyé en argument (qui sera case[i][j] dans cet exemple). Voir squelette de code.
⊲ Attention, le mieux est de déclarer son tableau à l’extérieur du main, en variable globale, valable
pour tout le programme ! Pour mieux comprendre, voici un squelette de code, à compléter, évidemment !
Squelette de code proposé
int cases[][] = new int[3][3]; //et autres variables globales nécessaires
// On aurait pu directement définir int cases[][] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0},};
// et éviter ainsi la fonction d’initialisation de la grille...
/****************************************
* FONCTIONS DE SAISIE ET DE TRAITEMENT *
****************************************/
void initialisationGrille() {
//une fonction qui met tout à 0 dans le tableau cases, c’est à dire chaque cases[i][j]=0
}
int saisieBlindeeCase() {
// il faut avoir des bonnes coordonnées et ne pas remplir de cases déjà pleines
}
int partieTerminee() {
/* teste si la partie est terminée,
* soit avec un vainqueur, en ligne, en colonne ou en diagonale;
* soit parce que la grille est pleine, encore qu’il suffit de compter le nombre de coups !
* Cette fonction renvoit
* 0 si la partie n’est pas terminée,
* 1 si elle l’est parce que les croix ont gagné,
* 2 si...
*/
}
/*************************
* FONCTIONS D’AFFICHAGE *
*************************/
String afficheCase(int entier) {
switch(entier) {
case 0 : return " ";
// à compléter évidemment
}
}
void affichageGrille() {
for (i =0; i <3; i ++) {
for (j =0; j <3; j ++) print("|" + afficheCase(cases[i][j]));
println("|");
}
}
/***********************
* PROGRAMME PRINCIPAL *
***********************/
void main() {
initialisationGrille();
/* A la fin le main est très court et appelle surtout les autres fonctions
* après chaque coup, on appelle affichageGrille();
* après chaque coup, on appelle partieTerminee(); et on interprète la valeur retournée
*/
}
ISN - Organiser son mini-projet - exemple du jeu du pendu
Ou « Quelques commentaires sur les variables de type String »
J’ai encore choisi cet exemple parce qu’on m’a posé la question, et parce que je ne voulais pas qu’ils se pendent.
⊲ Rappel : L’erreur classique du débutant est de se lancer dans le code et le mettre à jour progressivement, en
améliorant par touches, en implémentant de nouvelles fonctionnalités... Mais il est vrai que pour le pendu, il fallait
des trucs ou méthodes pour les String
⊲ Comment joue-t-on, concrètement ?
• Le programme choisit un mot, qui doit faire au moins trois lettres,
• car le programme propose un mot réponse constitué de la première et de la première lettre, et de « _ » à
la place des autres lettres.
• L’utilisateur du programme, que nous appellerons joueur, doit saisir une lettre.
• Si elle n’est pas dans les lettres à trouver la pénalité augmente,
• si elle est dans les lettres à trouver, la pénalité n’augmente pas et toutes les occurrences de la lettre doivent
apparaître à la place des « _ » correspondants.
• Le programme se termine quand on a tout trouvé,
• ou si la pénalité est trop grande !
Là encore, chacune de ces fonctionnalités bien séparées du programme doit faire l’objet d’une fonction
à l’extérieur de notre fonction principale main ! Voir squelette de code.
⊲ Ok, mais comment je gère la mémoire des coups ? Où je les stocke ?
Toujours la grosse question. Ici le mot à chercher est de type String.
Je propose, pour la première version, de l’imposer au début de programme : String mot = "ausgezeichnet";
On aura besoin de la longueur de ce mot, 13 ici, et d’un mot reponse qui est ici au début a___________t
⊲ Attention, le mieux est de déclarer ces trois variables à l’extérieur du main, en variable globale,
valable pour tout le programme !
⊲ Ok, mais comment je fais pour demander un caractère ?
Easy : javascool propose la fonction readChar(), un peu comme readInteger(), sauf qu’ici on renvoie un char.
Attention il ne faut pas taper sur la touche Entrée.
⊲ On va se contenter de compter les pénalités sans construire la potence pour commencer. Il faut une fonction pour
construire la réponse a___________t, une pour la modifier en a___e_e____et si on propose « e »...
⊲ Variables de type String :
• Ce n’est pas un type primitif, comme un char, c’est pour cela que cette classe commence par une majuscule.
• On peut faire des concaténations : mot = "Abde" + "raouf" donne mot = "Abderaouf".
• mot.length() donne la longueur du mot. Avec mot = "Abderaouf", mot.length() donne 9.
• mot.charAt(i) donne la i-ème lettre, i allant de 0 à 5 si le mot fait six lettres (comme pour les tableaux).
La première lettre de mot est mot.charAt(0), la deuxième mot.charAt(1), etc. D’où le (i+1) dans ce code :
void main() {
int i, longueur;
String mot = readString("Entrez votre mot:");
longueur=mot.length();
println("le mot saisi est : "+mot+" et il comprend "+ longueur+" lettres.");
for(i=0;i<longueur;i++) println("la "+(i+1)+"ème lettre du mot est "+mot.charAt(i) );
}
Essayez le avec Abderaouf ou ausgezeichnet ou Quarante-deux ! ;-)
• La méthode substring(int debut, int fin) permet d’extraire une sous-chaîne. Elle retourne un nouveau
String qui débute au caractère à la position debut et va jusqu’au caractère à la position (fin - 1).
Par exemple, si mot = "Abderaouf" alors txt = mot.substring(3, 6); retourne la chaîne "era".
• Enfin, pour tester si deux chaînes str1 et str2 sont les mêmes, if (str1.equals(str2))...
⊲ Pour mieux comprendre, voici à nouveau un squelette de code, à compléter, évidemment !
Squelette de code proposé
/**************************************
* Déclaration des variables globales *
**************************************/
String mot = "ausgezeichnet";
int longueur;
String reponse = ""; //ce sont trois variables globales
/*******************************************
* Fonction de construction du mot réponse *
*******************************************/
String constructionReponseInitiale() {
String construction = "";
/* ici on crée un mot construction à l’aide du mot de départ et des caractères _
* à l’aide d’une boucle for utilisant la variable globale longueur
* et de construction = construction + "_";
* et de mot.charAt(*) pour la première et la dernière lettre */
return construction;
}
String modificationReponse(char carac, int place) {
String nouvelle = /*construction à l’aide de reponse.substring(*,*) et carac
* à la place numéro place */
return nouvelle;
}
int traitement(char prop) {
/* Ici on traite la réponse en regardant si le caractère et dedans ou pas
* Si non, on renvoie une pénalité de 1.
* Si oui, on renvoie une pénalité de zéro,
* et on appelle la fonction modificationReponse pour remplacer les _
* pour chacune des apparitions de la lettre en question */
return penalite; // 0 ou 1 donc, suivant le cas
}
/***********************
* Programme principal *
***********************/
void main() {
clear();
longueur = mot.length();
//on doit savoir combien de lettres
reponse = constructionReponseInitiale();
//appel de la fonction
int compteur =0;
// pour les pénalités
char proposition;
// qui sera saisie par le joueur
println("Votre grille de jeu est :\n" + reponse+"\n"); // un \n revient à la ligne
while ( /* ce n’est pas gagné */ ) {
proposition = readChar(); //fonction de lecture javascool d’un caractère
compteur = compteur + traitement(proposition); //augmentera de 1 ou de zéro
println("La proposition est " + proposition + " et votre grille de jeu est :");
println(reponse+" et vous avez une penalite de " + compteur + "\n");
}
/* Code à finir
* C’est gagné ou pas */
}
Exemple d’exécution
Votre grille de jeu est :
a___________t
La proposition est a et votre grille de jeu est :
a___________t et vous avez une penalite de 1
La proposition est e et votre grille de jeu est :
a___e_e____et et vous avez une penalite de 1
La proposition est i et votre grille de jeu est :
a___e_ei___et et vous avez une penalite de 1
La proposition est o et votre grille de jeu est :
a___e_ei___et et vous avez une penalite de 2
La proposition est u et votre grille de jeu est :
au__e_ei___et et vous avez une penalite de 2
La proposition est y et votre grille de jeu est :
au__e_ei___et et vous avez une penalite de 3
La proposition est r et votre grille de jeu est :
au__e_ei___et et vous avez une penalite de 4
La proposition est s et votre grille de jeu est :
aus_e_ei___et et vous avez une penalite de 4
La proposition est g et votre grille de jeu est :
ausge_ei___et et vous avez une penalite de 4
La proposition est z et votre grille de jeu est :
ausgezei___et et vous avez une penalite de 4
La proposition est c et votre grille de jeu est :
ausgezeic__et et vous avez une penalite de 4
La proposition est h et votre grille de jeu est :
ausgezeich_et et vous avez une penalite de 4
La proposition est n et votre grille de jeu est :
ausgezeichnet et vous avez une penalite de 4
C’est gagné avec une penalité de 4 coups !
ISN - Squelette de code bien avancé pour le « Puissance 4 »
Les élèves en question n’étaient encore qu’à puissance 1, y’avait urgence.
⊲ Ce code est basé sur le code d’élèves, lui même dérivé du code du « Tic-Tac-Toe » déjà fourni. C’est une base à
copier/coller dans javascool, à finir, et à améliorer !
/**************************************
* Déclaration des variables globales *
**************************************/
int cases[][] = new int[6][7];
/*************************
* Fonctions d’affichage *
*************************/
void initialisationGrille() {
//on initialise la grille
for (int i = 0; i < 6; i ++) for (int j = 0; j < 7; j ++) cases[i][j] =0;
}
String afficheCase(int entier) {
//on choisis la valeur de "entier" pour lesquelles seront affichées x ou o
switch(entier) {
case 0 : return " ";
case 1 : return "x";
case 2 : return "o";
default : return " ";
}
}
void affichageGrille() {
clear();
//on affiche la grille
for (int i = 5; i >=0; i --) {
for (int j =0; j <7; j ++) print("|" + afficheCase(cases[i][j]));
println("|");
}
println("---------------");
}
/*******************************
* Fonctions de gestion du jeu *
*******************************/
int choixColonne(int c) {
//a modifier encore pour vérifier qu’une colonne soit plein ou pas !
int i =0, j =7,joueur = c %2 +1;
//là il faudra un boolean pleine = true avant la boucle while (pleine)
println("A vous de jouer, joueur " + joueur);
while (j <0 || j >6) j = readInteger("Choisissez la colonne dans laquelle vous mettez votre jeton
while (cases [i][j] > 0) i ++;
//un && i<6 à rajouter sinon ça va planter ... essayez de forcer sur une colonne...
cases[i][j] = joueur;
/* si i fait pas 6 et sinon ? Il faudra encore un while (pleine == true) bien placée
* qui d’ailleurs revient à while (pleine)
* si on peut remplir cases[i][j], alors pleine = false
* sinon on recommence
*/
return (c +1);
}
int partieTerminee() {
return 0;
/* evidemment il faut programmer les tests pour voir si c’est fini ou pas
* répondre 1 si joueur 1 a gagné et return 2 si c’est l’autre et 0 si on continue !
* il faut tester lignes, colonnes et diagonales ;-)
*/
}
/***********************
* Programme principal *
***********************/
void main() {
int compteur =0;
initialisationGrille();
while (compteur < 42 && partieTerminee() == 0) {
//six fois sept ? ou une autre question
affichageGrille();
compteur = choixColonne(compteur);
}
affichageGrille();
/* et là écrire qui a gagné ... ou pas ! */
}
Exemple d’exécution
| | | | | | | |
| | | | | | | |
| | | | | |o| |
| |o| | |o|x| |
| |o|o|o|o|x| |
|x|x|o|x|x|x|x|
--------------A vous de jouer, joueur 1
Notez que la victoire n’est pas encore gérée...
| | | | | |o| |
| | | | | |x| |
| | | | | |o| |
| |o| |X|o|x| |
| |o|o|o|o|x| |
|x|x|o|x|x|x|x|
--------------A vous de jouer, joueur 2
------------------Erreur lors de l’exécution de la proglet
java.lang.ArrayIndexOutOfBoundsException: 6
.choixColonne(JvsToJavaTranslated2.java:48)
.main(JvsToJavaTranslated2.java:77)
.run(JvsToJavaTranslated2.java:84)
------------------... et que si l’on instiste sur l’avant dernière colonne, la numéro 5, ça pose problème !