Big Tuto ANDROID

Chapitre 5 : L’écran de jeu, partie 2 : Le Code

 

Tutoriel présenté par : Stéphane Barthélémy (Stephantasy)
Date d'écriture : 10 septembre 2017
Date de révision : -

 

   1.   Introduction

 

   Ok, nous allons mettre l’interface de côté pour le moment et nous attaquer au code. wink

   Nous avons précédemment créé la base de notre jeu en construisant une interface graphique rudimentaire.

   À présent, nous allons ajouter le code du jeu Tic-Tac-Toe et nous verrons comment créer l’interaction avec notre interface. angel

   L’application est téléchargeable sur Google Play ! Vous aurez ainsi un aperçu du projet fini. wink

 


 

 

 

   2. Renommage

 

   Avant de commencer, nous allons renommer la classe de notre activité. En effet, à la fin du projet, il y aura une quinzaine de classe. Pour retrouver rapidement les classes d’activités, nous allons simplement ajouter le préfixe « Activity ».

   Pour renommer une classe, dans l’arborescence du projet, faites un clic droit sur la classe concernée et choisissez « Refactor > Rename… ». Entrez le nouveau nom « ActivityGameScreen » et validez en cliquant sur « Refactor ».

 

 


   3. La grille de jeu (Java)

 

   Nous allons à présent nous attaquer à la deuxième partie d’une activité : son code Java. Le code permet de donner vie à notre application. On pourrait diviser le code Java en deux parties : le Java classique, c’est-à-dire le code du jeu Tic-Tac-Toe, et le Java propre à Android.

 

   En ce qui concerne Java, je ne m’étendrai pas. Pour ceux qui ne connaissent pas le langage ou ceux qui ne sont pas familiers avec la POO (Programmation Orientée Objet), ça peut piquer un peu… indecision Néanmoins, le code est suffisamment commenté pour permettre de s’y retrouver. angel

   Aussi, le code du jeu Tic-Tac-Toe ne sera que survolé, ce n’est pas le but de ce tutoriel ! Bien sûr, pour ce qui est du code relatif à l’application Android, j’expliquerai son fonctionnement plus en détail. smiley

 


      A. Le code du jeu


   Le code du jeu Tic Tac Toe, ainsi que le projet Anroid Studio, vous sont fournis ici :

 

 

            i. Composition

 

   Nous allons commencer par intégrer le code du jeu. smiley

   Voici les éléments qui le composent :

   Pour la partie exclusivement réservée au jeu, nous avons une interface nommée « TicTacToe », qui contient les méthodes abstraites du jeu. La classe « Jeu » implémente cette interface. Nous avons également un énumérateur, nommé « Player », qui liste les joueurs.

   Ensuite nous avons une classe qui va gérer notre jeu. Elle se nomme, sans grande surprise, « GameManager » ! cheeky Cette classe s’interfère entre l’activité et le jeu. Elle ne parait pas indispensable de premier abord, car on aurait pu directement contrôler le jeu depuis l’activité ! surprise Cependant, l’utilité de cette architecture sera révélée dans le chapitre traitant de la persistance des données. wink

   Enfin, nous avons un énumérateur « AiResult » qui liste les différents états de l’intelligence artificielle du jeu. wink

 


            ii. Intégration

 

   Pour insérer des fichiers existants dans un projet, il suffit de les glisser depuis le dossier où ils se trouvent (le dossier Ch4_Code-du-jeu dans l'archive téléchargeable ci-dessus wink) vers « java > com.android.meruvia.mylittletictactoe » en pressant la touche « Ctrl ». Cela permet de « copier » les fichiers, plutôt que de les déplacer. wink

 


   De plus, en les copiant, Android Studio va automatiquement ajouter le nom du Package dans les fichiers Java ! wink

 

 


      B. Le code de l’activité

 

   Que les choses sérieuses commencent ! angel

   À toute activité est associée une classe Java. C’est cette dernière qui va démarrer l’activité et qui contiendra le code permettant sa gestion.

   Ouvrez le fichier Java de notre activité « GameScreen » qui se trouve dans « app > java > com.android.meruvia.mylittletictactoe ».

   Cette classe doit obligatoirement dériver de « AppCompatActivity ». Lors du démarrage d’une activité, la méthode « onCreate » est appelée automatiquement et c’est elle qui lance l’interface associée avec la commande suivante :

setContentView(R.layout.activity_game_screen);

   C’est ce que nous avons vu précédemment lors de l’exécution de notre activité avec l’émulateur. wink

 


            i. Contrôle d’un élément graphique

 

   À présent, nous allons appeler le code du jeu depuis notre activité et créer une interaction avec l’interface graphique. Pour cela, il va nous falloir prendre le contrôle des différents éléments la composant. En général, c’est principalement ce qu’on fait dans la fonction « onCreate ».

   Nous allons avoir besoin :

- De chaque case de la grille,
- Du texte affichant le résultat de la partie,
- Du bouton de remise à zéro de la partie.

   Et puisque nous allons nous en servir dans plusieurs fonctions, nous allons créer des variables globales du type de l’objet cible, soit :

private List<Button> listButton;             (Une liste de Button)
private TextView tvGameResult;               (Un TextView)
private Button btNewGame;                    (Un Button)


   Ajoutez ces 3 lignes au-dessus de la fonction « onCreate » et constatez qu’Android Studio affiche « Button » et « TextView » en rouge. La raison est qu’il faut importer les classes associées. Pour cela, cliquez n’importe où dans le code, sauf sur les noms en rouge ! Un Pop-up devrait apparaitre pour vous permettre d’insérer ce qu’il faut :

 



   Puis faites « Alt + Entrée », et Android Studio ajoute les éléments manquants :

 



   À présent, nous pouvons associer nos variables aux objets graphiques. Pour cela, une seule fonction : « findViewById() ». On l’utilise en « castant » le type de l’objet cible et en passant son « Id » en paramètre. Ce qui donne pour le TextView :

tvGameResult = (TextView)findViewById(R.id.tv_result);

 

   Lors de la compilation, Android Studio crée une seule classe contenant tout le code de l’application : c’est la classe « R ». Tous les éléments sont associés à un identifiant qui se retrouve dans cette classe. Ainsi, pour trouver l’identifiant de notre TextView, on fait appel à « R », suivi de « id » (qui contient tous les identifiants), suivi de l’Id recherché (celui déclaré dans « activity_game_screen.xml »).

   On fait la même chose pour le bouton, cette fois en castant « Button » : 

btNewGame = (Button)findViewById(R.id.bt_new_game);

 

   Pour des raisons pratiques, les boutons de la grille sont placés dans une « List » et on procède de la même manière pour chacun des boutons :

listButton = new ArrayList<>();
listButton.add((Button)findViewById(R.id.bt_case_0));
listButton.add((Button)findViewById(R.id.bt_case_1));
listButton.add((Button)findViewById(R.id.bt_case_2));
listButton.add((Button)findViewById(R.id.bt_case_3));
listButton.add((Button)findViewById(R.id.bt_case_4));
listButton.add((Button)findViewById(R.id.bt_case_5));
listButton.add((Button)findViewById(R.id.bt_case_6));
listButton.add((Button)findViewById(R.id.bt_case_7));
listButton.add((Button)findViewById(R.id.bt_case_8));

 

   Android Studio est assez performant avec la « complétion automatique » (ou auto-completion en anglais). Par exemple, tapez « f » : « findViewById() » est le premier élément de la liste. Appuyez sur « Entrée » et le nom de la fonction est écrit directement ! cool Avec l’habitude, on crée rapidement du code avec cette méthode. wink

   Maintenant que nous avons nos variables, tapez-en une au hasard, suivit d’un point et faites « Ctrl + Espace ». Voyez la longue liste de contrôles qui nous est offerte ! cheeky Évidemment, la liste diffère en fonction de l’objet.

 

 


            ii. Remplissage du TextView

 

   Lorsqu’on lance notre application avec l’émulateur, notre grille contient des tirets, notre TextView affiche « Result » et notre bouton affiche « NEW GAME » (et il ne se passe rien quand on clique dessus).

   Nous allons modifier le texte du TextView au démarrage de l’application en lui affectant de nouveaux caractères. Cela est aussi simple que :

tvGameResult.setText("Nouveau texte");


   Et voilà, ce n’est pas plus compliqué ! cheeky Relancez l’application pour voir le nouveau texte s’afficher. wink

   C’est une bonne idée de tester régulièrement son application avec l’émulateur, surtout au début, lorsque l’on ne connait pas encore bien le fonctionnement d’Android. Faites-le après chaque tâche spécifique, cela vous évitera d’avoir un cumul de bugs ! laugh

 


            iii. Événement du bouton

 

   Maintenant, faisons quelque chose lorsqu’on appuie sur le bouton « New Game ».

   En fait, il existe 5 méthodes différentes pour récupérer l’événement « clic » d’un bouton… Nous n’en verrons que 2 :

- En utilisant une classe anonyme,
- En pointant une méthode.

 

 

                 a) Classe anonyme

 

   Nous allons ajouter un écouteur à notre bouton. Tapez le nom de la variable « btNewGame », suivi d’un point, puis sélectionnez « setOnClickListener » dans la liste (commencez à taper le nom de la méthode pour l’afficher plus rapidement).

   En paramètre de cette méthode, on doit passer l’interface « OnClickListener » (disponible dans la classe « View ») et en redéfinir sa méthode « onClick ». Ce qui donne :

btNewGame.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
             initialize();
         }
     }
);


   Voilà, c’est fait ! wink Dès lors, chaque fois que l’on va cliquer sur le bouton, ce qui se trouve dans « onClick » sera exécuté. Quoi ?! surprise Ah mais oui, il n’y a rien dans la méthode ! cheeky

   Et bien dans ce cas, modifions le TextView tiens ! wink Il suffit d’y insérer :

tvGameResult.setText("Nouvelle partie !");


   Faites le test ! Au démarrage de l’application, le TextView contient « Nouveau texte » et lorsqu’on clique sur le bouton, le texte est remplacé par « Nouvelle partie ! ». wink

 

 

                b) Pointer une méthode

 

   Utilisons l’autre façon de procéder pour la grille. Nous allons donc associer une méthode à l’événement « clic » des boutons. Pour commencer, ajoutez la méthode suivante dans la classe « GameScreen » :

public void action_grille(View v){
    String leTexte = "Id de la case : " + v.getId();
    tvGameResult.setText(leTexte);
}

   À noter que pour associer une méthode à un objet, la signature de la méthode sera toujours identique à celle-là ! Il est obligatoire d’avoir pour seul paramètre « View ». De plus, elle doit être publique et ne rien retourner. La raison est que l’objet va se passer lui-même en paramètre et nous permettra de savoir qui a appelé cette méthode ! wink

   À savoir également que tous les objets graphiques héritent de « View ».

   Ensuite, il faut retourner dans « activity_game_screen.xml » et associer notre méthode à la propriété « onClick » de chaque bouton de la grille. Pour cela, 2 méthodes déjà évoquée :

- Depuis le mode « Design »

 



- Depuis le mode « Text »

 



   Une fois les neuf boutons faits, relancez l’application dans l’émulateur et appuyez sur chacun d’entre eux pour voir l’identifiant du bouton s’afficher.

   Vous aurez peut-être remarqué au passage que, lorsque le texte s’affiche, le bouton « New Game » s’agrandit ! surprise (Cliquez sur ce bouton et voyez également que le texte n’est pas centré.) Prenez note que nous devrons corriger cette bizarrerie lorsqu’on retournera finir notre interface ! wink

 

            iv. Fin des tests

 

   Maintenant que nous avons vu comment cela fonctionne, nous allons supprimer le code de test et ajouter ce dont on a besoin pour faire fonctionner notre jeu. wink

   Dans la méthode « onClick » de « btNewGame », remplacer le code qui s’y trouve par : « initialize(); ». Ensuite, créez cette méthode privée dans laquelle on va :

- Vider la grille
- Vider « tvGameResult »
- Initialiser le jeu

   Ce qui donne :

private void initialize(){

    // On vide de la grille
    for( Button b : listButton){
        b.setForeground(null);
        b.setText("");
    }

    // On vide le message de fin de partie
    tvGameResult.setText("");

    // On initialise le jeu
    gameManager.initialiser();
}

 

   Ah oui ! surprise Pour l’initialisation du jeu, nous avons besoin d’un objet, instance de la classe « GameManager ». Il faut donc ajouter cette déclaration avec les variables globales :

GameManager gameManager = new GameManager();


   On ajoute également un appel à la méthode « initialize() » dans « onCreate », afin d’initialiser le jeu au démarrage de l’application (n’oubliez pas que cette méthode est appelée lorsqu’on lance l’application, avant que l’interface ne s’affiche wink).

   Pour finir, supprimez le contenu du la méthode « action_grille », nous allons y insérer l’appel à la logique du jeu ! wink

 

 

      C. Appel du code du jeu

 

   La méthode « action_grille » contient le code qui fait le lien entre l’activité et la logique du jeu.

   Cela se résume à peu de chose ! cheeky Lorsqu’on appuie sur un bouton dans la grille, on lui affecte un « X ». Ensuite, c’est au tour du jeu de jouer (« O »).

 

 

            a) Logique

 

   Le joueur « X » (vous) commence toujours. Puis c’est au tour de l’IA (« O »). Après que le joueur « O » ait joué, on vérifie le résultat de la partie et on se retrouve avec 5 possibilités :

- La partie n’est pas finie, le joueur « O » vient de jouer, on met un « O » dans la case,
- La partie est finie, match nul, on affiche le résultat,
- La partie est finie, « X » a gagné, on affiche le résultat,
- La partie est finie, « O » a gagné, on affiche le résultat,
- La partie est finie, le résultat est déjà affiché, donc on ne fait rien.

   Pour que l’IA sache ce qu’il en est de la partie, elle a besoin de connaitre la position du bouton dans la grille qui vient d’être pressé, selon ce schéma :

0      1       2
3      4       5
6      7       8

 

 

            b) Ajout d’un « tag »

 

   Lorsque nous appuyons sur un bouton, nous devons indiquer à l’IA l’indice correspondant : soit un chiffre de 0 à 8.

   Nous allons donc affecter un « tag » à chacun de nos boutons dans l’interface graphique. Pour cela, retournons dans le fichier « activity_game_screen.xml » et ajoutons cette ligne à chaque bouton, où « i » est le chiffre correspondant à la position :

android:tag="i"


   Ce qui donne pour le premier bouton, en haut à gauche :

<Button
    android:id="@+id/bt_case_0"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="action_grille"
    android:tag="0"
    android:text="@string/case_tiret" />

 

   La propriété « tag » sert à donner une information supplémentaire à un objet. L’IA utilise une matrice avec les chiffres de 0 à 8, ainsi, grâce au tag, on évite d’avoir à utiliser une quelconque mécanique permettant d’associer le bon chiffre au bon bouton ! wink

 

 

            c) Lecture du « tag »

 

   Tel que nous l’avons vu précédemment, chaque bouton appelle la fonction « action_grille » en se passant en paramètre. Nous savons donc quel bouton a créé l’événement. Pour récupérer le « tag » associé, nous allons utiliser la méthode « getTag() », tout simplement ! wink

    Button bt = (Button)v;   // "v" est le paramètre passé à la fonction
    int buttonPosition = Integer.parseInt(bt.getTag().toString()); 

   Étant donné qu’on reçoit un View en paramètre, il nous faut d’abord le Caster pour en faire un bouton. Ensuite, puisque le Tag est une String, nous devons le transformer en Integer, car c’est ce que prend en paramètre l’IA ; c’est ce que fait « Integer.parseInt ».

   Voici le code de la méthode « action_grille » au complet : 

     public void action_grille(View v){
        Button bt = (Button)v;
        int buttonPosition = Integer.parseInt(bt.getTag().toString());

        // On s'assure que la case est libre
        if(!gameManager.isFinished() && bt.getText().toString().equals("")){

            // Placement du joueur X
            bt.setText("X");

            // On interroge l'IA pour connaitre la suite des événements
            aiResult = gameManager.ia(buttonPosition);
            switch(aiResult){

                // O a joué
                case AI_O_PLAYED:
                    // On affiche le O sur la grille
                    listButton.get(gameManager.getOLastShot()).setText("O");
                    break;

                // Partie nulle
                case AI_DRAW:
                    // On affiche que la partie est nulle
                    tvGameResult.setText(getResources().getString(R.string.result_draw));
                    break;

                // X a gagné
                case AI_X_WON:
                    // On affiche que le joueur a gagné
                    tvGameResult.setText(getResources().getString(R.string.result_win));
                    break;

                // O a gagné
                case AI_O_WON:
                    // On affiche d'abord le O sur la grille
                    listButton.get(gameManager.getOLastShot()).setText("O");
                    // Puis, on affiche que le joueur a perdu
                    tvGameResult.setText(getResources().getString(R.string.result_lose));
                    break;

                // Rien de spécial
                case AI_NOTHING:
                default:
                    break;
            }
        }
    }

 

   Voyez qu’il n’y a rien de sorcier dans ce code. La seule nouveauté est la suivante :

getResources().getString(R.string.result_draw)

 

   Ceci permet d’aller cherche une chaine de caractère dans le fichier « string.xml ». À bien y regarder, cette commande ressemble un peu à celle qui permet de récupérer l’identifiant d’un objet, souvenez-vous : 

btNewGame = (Button)findViewById(R.id.bt_new_game);


   Sauf qu’ici, on ne veut pas un Id, mais une String ! Et tout comme on le fait pour un objet, on récupère une chaine de caractères en précisant son identifiant (celui déclaré dans le fichier XML).

   En passant, il ne faut pas oublier d’ajouter les 3 textes suivants dans le fichier « string.xml » :

<string name="result_draw">Draw...</string>
<string name="result_win">You Win!</string>
<string name="result_lose">You Lose!</string>

 


      D. Test final

 

   Et voilà, il ne vous reste plus qu’à tester tout ça avec l’émulateur ! angel

   À noter que l’IA ne vous permet pas de gagner… frown Eh oui, désolé ! laugh Au mieux, vous pouvez faire match nul. Nous verrons dans un chapitre ultérieur comment ajouter un niveau de difficulté. wink

 

 

      E. Conclusion

 

   Ça y est, nous avons une application fonctionnelle ! angel On pourrait s’arrêter là, mais il y a encore de nombreuses choses que l’on peut améliorer. (Si, si, je vous assure ! cheeky)

   Nous allons commencer par la rendre un peu plus présentable en corrigeant le problème du bouton qui se redimensionne. Ensuite, nous allons centrer le texte du résultat. Rien de bien méchant ici.

   Ensuite, nous allons parer notre (hideuse) interface avec de jolies couleurs ! wink Nous mettrons également en lumière la combinaison gagnante en lui appliquant une couleur différente lorsque la partie a été gagnée. wink

   Voilà le programme du prochain chapitre ! smiley

 

 

      F. Plus tard…

 

   Suite à cela, ne vous croyez pas à l’abri des problèmes, car il y en a encore ! indecision Un exemple ? angry Lancez l’émulateur et jouez un coup ou deux. À présent, tournez l’appareil avec un des boutons appropriés (« Rotate left » ou « Rotate right ») :

 



   Oh ! Tout a disparu ! On a perdu la partie en cours… surprise

   Nous reviendrons sur cet élément crucial du développement d’une application Android ! wink

   A bientôt pour le chapitre 5 ! angel

         Stephantasy

   

 

 

This site uses cookies to enable you to log in. We do not store or sell any personal data. By continuing to use this website, you agree to their use. Thanks!