IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Corrections des exercices du livre "Java pour les enfants, les parents et les grand-parents"


précédentsommairesuivant

VI. Chapitre 7

VI-A. Exercice 1 : nombre de victoires/défaites

VI-A-1. Analyses et stratégies

On nous demande d'ajouter au panneau supérieur deux champs affichant le nombre de victoires et de défaites (code ajouté en marron).

Nous allons essayer d'aboutir au résultat suivant :

Image non disponible
Aperçu de l'applet

On peut ajouter les champs en tant que composants Label, dont les textes seront mis à jour à chaque fin de partie. On aura aussi besoin de garder une référence sur les deux composants, afin que les différentes méthodes de la classe puissent en modifier le texte. J'en fais donc des variables de classe et j'y déclare ces deux composants, mais en dehors de toute méthode.

 
Sélectionnez
    Button cases[];
    Button boutonNouvellePartie;
    Label score;
    Label labelVictoires;
    Label labelDefaites;

Mais il convient également de réfléchir quant à l'agencement des composants dans le panneau supérieur. En effet, on n'aura plus affaire à un seul Button qui en occupera tout l'espace, mais à trois composants qu'il faudra faire coopérer le plus harmonieusement possible.

Pour ma part, j'ai opté pour un GridLayout à trois lignes et une colonne. Donc, avant de créer et ajouter les composants, je définis le LayoutManager :

 
Sélectionnez
        // Cree le bouton Nouvelle partie et enregistre
        // le recepteur d'actions aupres de lui
        boutonNouvellePartie = new Button("Nouvelle partie");
        boutonNouvellePartie.addActionListener(this);
        // Cree deux panneaux et un label et les agence en
        // utilisant le border layout
        Panel panneauSuperieur = new Panel();
        panneauSuperieur.setLayout(new GridLayout(3,1));
        // Cree les deux labels victoires/defaites
        labelVictoires = new Label("Nombre de victoires : 0");
        labelDefaites = new Label("Nombre de defaites : 0");
        // Ajoute les composants au panneau superieur
        panneauSuperieur.add(labelVictoires);
        panneauSuperieur.add(labelDefaites);
        panneauSuperieur.add(boutonNouvellePartie);
        this.add(panneauSuperieur, "North");

On nous recommande également d'utiliser deux variables de classes et de les utiliser pour comptabiliser le nombre de victoires et de défaites :

 
Sélectionnez
    int casesLibresRestantes = 9;
    int nombreVictoires = 0
    int nombreDefaites = 0;

Enfin, il ne nous reste plus qu'à incrémenter le nombre de victoires/défaites juste en fin de partie et de mettre à jour les Labels correspondants :

 
Sélectionnez
    } // Fin de la boucle for
        if (gagnant.equals("X")) {
            score.setText("Vous avez gagne !");
            nombreVictoires++;
            labelVictoires.setText("Nombre de victoires : "+nombreVictoires);
        } else if (gagnant.equals("O")) {
            score.setText("Vous avez perdu !");
            nombreDefaites++;
            labelDefaites.setText("Nombre de defaites : "+nombreDefaites);
        } else if (gagnant.equals("T")) {
            score.setText("Partie nulle !");
        }
    } // Fin de actionPerformed

Vous connaissez déjà l'opérateur d'incrémentation ++, car vous l'avez déjà utilisé pour la boucle for.

 
Sélectionnez
NombreVictoires++;

équivaut à

 
Sélectionnez
nombreVictoires = nombreVictoires + 1;

VI-A-2. Code complet

Nouvelle version du morpion (ex1)
TéléchargerCacher/Afficher le codeSélectionnez

VI-B. Exercice 2 : résolution d'un bogue

VI-B-1. Quelques astuces

Un bogue grave subsiste : on peut modifier la valeur d'une case déjà jouée !

Pour remédier à ceci, il faut modifier le gestionnaire de clics. Qui dit gestionnaire de clics, dit méthode actionPerformed de l'interface ActionListener.

Il suffira donc, dans la méthode actionPerformed, de n'exécuter la modification du bouton cliqué uniquement si son texte est vide.

Mais d'abord, il ne faut pas perdre de vue que les chaines de caractères ne sont pas de simples types primitifs (à l'instar des int, boolean,…), mais des données de type Object. Et la meilleure manière de comparer deux objets (on suppose ici que objet1 et objet2 sont comparables, ce qui est le cas si objet1 et objet2 sont du même type ou si objet1 et objet2 ont au moins une classe mère commune), c'est de faire appel à la méthode equals() de l'un des objets :

 
Sélectionnez
objet1.equals(objet2) ;
objet2.equals(objet1) ;

Donc, si j'ai une chaine de caractère chaine1 et que je veuille tester si elle est vide, je pourrais écrire :

 
Sélectionnez
chaine1.equals("") ;

Enfin le texte d'un composant java.awt.Button s'obtient en appelant sa méthode getLabel().

VI-B-2. Méthode actionPerformed corrigée

La nouvelle version de la méthode actionPerformed()
Cacher/Afficher le codeSélectionnez

VI-B-3. Code complet

Nouvelle version du morpion (ex2)
TéléchargerCacher/Afficher le codeSélectionnez

VI-C. Exercice 3 : ajout d'une méthode main()

Cet exercice-là peut s'avérer beaucoup plus compliqué qu'il n'y paraît. On nous demande de nous baser sur l'applet existante afin de coder une application à part entière : on doit pouvoir l'exécuter sans l'intégrer dans une page HTML.

Dans la méthode init() de l'applet, on a construit les différents composants. On les a également ajoutés à l'applet : c'est-à-dire à une instance de java.awt.Container. En effet, la javadoc nous apprend que la classe java.applet.Applet est une classe fille de java.awt.Container, même si c'est de manière indirecte.

Or si l'on veut que le jeu puisse être lancé en mode application, il faudra créer nous-même la fenêtre, y ajouter une instance de Panel et ajouter les composants à ce Panel (ainsi qu'ajouter le Panel à la fenêtre). Et la classe java.awt.Panel hérite notamment de la classe java.awt.Container. Il faudra donc adapter tout ce qui a été fait dans la méthode init() pour n'importe quel objet Container.

VI-C-1. Code de la méthode constuireContainer()

J'ai donc créé une méthode construireContainer() dans la classe Morpion : elle prend en paramètre un Container et en remplit le contenu avec les composants de notre jeu Morpion.

Souvenez-vous que dans le cas d'une Applet, le conteneur parent de tout composant est l'Applet elle-même. Évidemment, dans le cas de l'application, cela sera juste un Panel : lequel sera ajouté au LayoutManager de la fenêtre.

La méthode construireContainer()
Cacher/Afficher le codeSélectionnez

N'oubliez pas que dans le code original, this désigne l'instance de Morpion courante : il suffit donc, si cela est approprié, de le remplacer par container.

VI-C-2. Nouvelle version de la méthode init()

Le code de la méthode init() devient donc :

 
Sélectionnez
    public void init() {
        construireContainer(this);
    }

On peut déjà, à ce stade, vérifier que l'applet fonctionne de la même manière qu'auparavant.

Maintenant on dispose d'une manière simple de remplir un Panel avec le contenu de notre jeu Morpion. On peut se mettre à coder la création de la fenêtre, dans la méthode main().

L'utilisation d'une fenêtre au lieu du codage d'une applet a tout de même quelques répercussions :

  • il convient de définir, pour la fenêtre, certains aspects dont on ne se préoccupait pas en développant une applet : le titre de la fenêtre (jusque-là définit par la page HTML), la position de la fenêtre, la taille de la fenêtre… ;
  • il faut prévoir un moyen de fermer la fenêtre si l'utilisateur clique sur son bouton de fermeture ;
  • c'est à nous-même d'afficher la fenêtre.

Afin de prévoir une fermeture de la fenêtre, il nous faut ajouter un WindowListener à la fenêtre créée et coder dans la méthode windowClosing() la fermeture proprement dite. Pour mettre fin à l'application, on appellera :

 
Sélectionnez
System.exit(0) ;

Passer 0 signifie que le programme s'est finalement déroulé normalement. Passer une valeur supérieure à 0 signifie que le programme a du finir précipitamment. C'est une convention.

La classe WindowListener présentant de nombreuses méthodes dont nous n'aurons pas besoin, on peut se tourner vers la classe WindowAdapter. Il nous suffira alors de redéfinir la méthode windowClosing(). J'attire votre attention sur le fait que la méthode windowClosed() est appelée une fois que la fenêtre a été fermée : alors que pour windowClosing(), on se contente de détecter si la fermeture de la fenêtre a été demandée.

VI-C-3. Code de la méthode main()

Code de la méthode main()
Sélectionnez
    public static void main(String[] args) {
        Frame fenetre = new Frame();
        /*
         * Cette fois-ci, c'est le code qui
         * fixe les dimensions de la zone de jeu,
         * et non une page html.
         */
        fenetre.setSize(400, 300);
        /*
         * Cette fois-ci, le titre se definit dans la fenetre.
         */
        fenetre.setTitle("Jeu du morpion");
        /*
         * Centre la fenetre sur l'ecran
         */
        fenetre.setLocationRelativeTo(null);
        Panel panneauFenetre = new Panel();
        fenetre.add(panneauFenetre);
        
        Morpion logiqueMorpion = new Morpion();
        logiqueMorpion.construireContainer(panneauFenetre);
        
        fenetre.addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
            
        });
        
        fenetre.setVisible(true);
    }

VI-C-4. Code complet

Nouvelle version du morpion (ex3)
TéléchargerCacher/Afficher le codeSélectionnez

Voici ce que le programme peut donner en version fenêtre :

Image non disponible
Version fenêtre

VI-D. Exercice pour les petits malins

VI-D-1. Les tableaux multidimensionnels

VI-D-1-a. Théorie

Jusqu'ici vous n'avez utilisé que les tableaux à une seule dimension, par exemple :

 
Sélectionnez
int monTableau[] = new int[6] ;
int monTableau[] = {2,4,6,8,10,12} ;

Les deux tableaux précédents ont la même dimension.

Mais en fait, un tableau peut avoir, en théorie, une infinité de dimensions :

 
Sélectionnez
int monTableau3D[][][] = new int [2][6][3] ;
int monTableau3D[][][] = {
{ {5,1,12},{2,-6,18},{8,0,-54},{1,-5,-67},{3,10,20},{0,78,27} },
{ {23,1,134},{25,6,12},{99,6,54},{19,100,-67},{37,-2,18},{5,72,82} }
};

Ce sont deux tableaux à trois dimensions et presque équivalents : c'est juste que le premier tableau n'est rempli qu'avec des zéros.

Comment alors parcourir un tel tableau ? Il suffit d'utiliser plusieurs boucles for imbriquées : la première boucle for pour parcourir la première dimension, la deuxième pour parcourir la deuxième dimension et la dernière pour parcourir la dernière dimension :

 
Sélectionnez
for (int dim1 = 0 ; dim1 < monTableau3d.length ; dim1++){
    for (int dim2 = 0 ; dim2 < monTableau3D[dim1].length ; dim2++){
        for (int dim3 = 0 ; dim3 < monTableau3D[dim1][dim2].length ; dim3++){
            System.out.println(monTableau3d[dim1][dim2][dim3]) ;
        }
    }
}

Certains auront sans doute remarqué comment j'ai consulté la longueur de chaque dimension dans les différentes boucles :

 
Sélectionnez
monTableau3d.length // pour la première dimension
monTableau3D[dim1].length // pour la deuxième dimension
monTableau3D[dim1][dim2].length // pour la troisième dimension

En effet, il faut faire attention à deux choses :

  • si l'on écrit juste montableau3d.length, on n'obtient que la longueur de la première dimension ;
  • on réutilise les variables dim1, dim2 car il se peut que le tableau ne soit pas de dimension uniforme (régulière).

Pour vous en convaincre, essayez de déclarer le tableau comme ceci :

 
Sélectionnez
int [][][] monTableau3D = {
    { {1,2,3}, {4}, {5,6,7,8}, {9,10} },
    { {11}, {12,13}, {14,15,16}, {17,18, 19,20} }
};

Testez-le aussi avec l'extrait de code avec les trois boucles imbriquées, que j'ai donné peu avant. Vous constaterez alors que :

  • il n'y a aucune erreur à la compilation (depuis Eclipse, rien ne nous est signalé avant le test) ;
  • il n'y a aucune erreur à l'exécution et on a bien l'ensemble des valeurs affichées, dans l'ordre.

Le tableau que vous avez testé est un tableau multidimensionnel légals et la triple boucle imbriquée que je vous ai introduit est tout à fait adaptée pour le tester. Si on ne se basait pas sur la valeur courante de chaque dimension lors des différents tests de longueur des dimensions, on pourrait alors facilement aboutir à une erreur d'exécution : ArrayOutOfBoundsException.

Donc ceci :

 
Sélectionnez
for (int dim1 = 0 ; dim1 < monTableau3d.length ; dim1++){
    for (int dim2 = 0 ; dim2 < monTableau3D[0].length ; dim2++){
        for (int dim3 = 0 ; dim3 < monTableau3D[0][0].length ; dim3++){
            System.out.println(monTableau3d[dim1][dim2][dim3]) ;
        }
    }
}

est à proscrire pour le tableau irrégulier que je viens de vous donner.

On nous demande ici d'utiliser un tableau à deux dimensions, ce qui ne devrait pas poser plus de problèmes que pour le tableau à trois dimensions que l'on vient de voir.

Enfin, une dernière difficulté technique que l'on se doit de résoudre. La section du code original suivante :

 
Sélectionnez
        // Regarde si la ligne 2 a 2 cases identiques et une vide

        if (poids[3] + poids[4] + poids[5] == deuxPoids) {

            if (poids[3] == 0)
                return 3;
            else if (poids[4] == 0)
                return 4;
            else
                return 5;
        }

peut nous poser problème. On passe en effet d'un tableau où les neuf cases sont alignées à un tableau à deux dimensions. Il faut donc trouver un moyen de convertir ("à la main") tous les indices qui étaient prévus pour un tableau à une dimension, en une paire d'indices pour notre nouveau tableau.

VI-D-1-b. Mise en pratique

La première question à se poser : la première dimension représentera-t-elle les lignes ou les colonnes ? Pour le savoir, il faut se baser sur la manière dont les boutons sont ajoutés au conteneur. Le panneauCentral, sur lequel sont ajoutés les boutons, a un agencement défini par un GridLayout de trois lignes par trois colonnes.

Les boutons seront donc ajoutés colonne par colonne, en passant à la ligne suivante au besoin. La boucle for la plus interne devrait donc être consacrée au parcours de colonnes. On en déduit donc qu'il sera plus commode d'attribuer les lignes à la première dimension (revoyez au besoin le code de parcours du tableau à trois dimensions : la boucle for la plus externe examine la première dimension du tableau).

La deuxième question à se poser : comment obtenir la ligne et la colonne à partir de l'index initial ?

On peut remarquer que :

 
Sélectionnez
colonne = index % 3 ; // reste de la division entière de index par 3
ligne = index / 3 ; // quotient de la division index / 3 => c'est un entier
en assumant qu'index est une variable de type int.

Par exemple, pour l'index 5 :

 
Sélectionnez
colonne = 5 % 3 = 2
ligne = 5/3 = 1

Au lieu d'écrire cases[5], on écrira donc cases[1][2]

VI-D-2. Code résultat

Nouvelle version du morpion (exMalins)
TéléchargerCacher/Afficher le codeSélectionnez

VI-E. Synthèse

Ces exercices ont pu vous apprendre :

  • à ajouter des composants dans une interface AWT et les mettre régulièrement à jour grâce à l'utilisation de variables de classe ;
  • à résoudre simplement un bogue ;
  • à rendre une applet utilisable en mode application, en y ajoutant une méthode main(). Mais aussi à adapter au besoin le code ;
  • à utiliser un tableau à deux dimensions, sans obtenir d'erreur à l'exécution.

précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2012 Laurent Bernabé. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.