vendredi 19 décembre 2014

Atelier Pratique - Le démineur



Bonjour chers lecteurs,
Aujourd’hui nous vous proposons un article sous forme d’atelier pratique. Toutefois, il est conseillé d’avoir suivi tous nous autres articles concernant le JAVA avant de tenter de réaliser cet atelier.
En effet nous allons réaliser un prototype un peu moins avancé du célèbre jeu Windows :
J’ai nommé « Le démineur ».

Nous allons traiter uniquement la partie remplissage de la carte et ne nous occupons donc pas de la partie saisie.



Tout d’abord je vais vous rappeler les règles de ce jeu. Lorsque vous appuyez sur une case, celle-ci nous indique le contenu de la case. Il peut s’agir de chiffres qui indiquent le nombre de bombes avec lesquelles la case choisie est en contact, ceci vaut pour les huit directions possibles : nord, nord-est, est, sud-est, sud, sud-ouest, ouest et nord-ouest.
Deuxièmement, il peut s’agir d’une bombe. Si vous en touchez une, la partie est terminée.
Finalement, la case peut être neutre et donc vide.

Afin de réaliser notre mini-Démineur, il est primordial d’analyser le problème en tenant compte des règles citées précédemment.

Bien je vais  vous laisser chercher par vous mêmes, la solution se trouve ci-dessous.

Comment allons-nous procéder ?

Nous devons avant tout initialiser un certain nombre de variables qui nous serviront tout au long de notre programme, puis nous générerons la carte en y plaçant un certain nombre de bombes. Ce nombre sera stocké dans une variable, afin de pouvoir le modifier dans le cas où nous envisageons d’ajouter un degré de difficulté à notre jeu.
Finalement, nous irons saisir pour chaque case, le nombre de bombes qui entourent celle-ci.
Vous êtes toujours motivés j’espère, au boulot !

De quoi avons-nous besoin ?

Pour réaliser ce tutoriel nous allons devoir employer toutes les notions vues jusqu’ici en matière de Java. Commençons par initialiser nous variables et notre carte qui n'est autre qu'une matrice. 
Nous partions du principe que les cases vides sont représentes par des 0 et les bombes par des 9.


 // Signe de la bombe
 public static final char bombe = 9;
    
    //Fonction main
    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);

        //déclarationde variables
        
        int randLigne, randCollone, cpt;    //variables de génération du tableau
        int maxLigne = 10;                  //Nombre  de lignes pour notre carte
        int maxCollone = 10;                //Nombre de Collones pour notre Carte
        int nbrBombes = 10;                 //Nombre de bombes dans la carte
        
        
        // Initialisation de la matrice qui nous servira de Carte
        int tableau[][] = new int[maxLigne][maxCollone];

COMMENT PLACER LES BOMBES ?

Maintenant que nous avons toutes les variables et notre matrice, il s'agit  d'initialiser le tout ensemble en y plaçant les bombes. Nous procédons en tirant une ligne au sort et une colonne au sort  puis nous plaçons notre bombe. Seul petit détail à prendre en compte est qu'il ne faut pas placer une bombe dans une case qui en contient déjà une.
  System.out.println("BIENVENU AU SUPER MINI-DEMINEUR !!!");
  System.out.println();
  
  
        //PLACEMENT DES BOMBES
        cpt = 0;
        while (cpt < nbrBombes) {

            //Création aléatoire de l'endroit où nous plaçons nos bombes
            randLigne = (int) (Math.random() * (maxLigne));
            randCollone = (int) (Math.random() * (maxCollone));

            
            // Si la case ne contien pas de bombe on en met une puis on incrémente notre compteur de bombes
            if (tableau[randLigne][randCollone] != bombe) {
                tableau[randLigne][randCollone] = bombe;
                cpt++;

            }
        }

COMMENT REMPLIR LES INDICATEURS

Nous avons maintenant une carte avec des bombes, mais celle-ci ne nous dit pas combien de  bombes il y a autour de chaque case.
 Vous ne le savez peut-être encore, mais nous sommes confrontés à un problème. Nous devons en effet aller contrôler toutes les cases qui entourent chaque case.
Si je prends l'exemple de la case 2, nous devons contrôler s'il y a une bombe ou pas dans les cases, 
un, trois, quatre, cinq et six. Toutefois, vous l'aurez remarqué  les trois cases ou c'est écrit "RIEN" n’existent pas  et donc le compilateur va crasher si vous essayez de connaitre leur contenu.  Nous constatons donc que de connaitre l'emplacement de la case que nous remplissons est crucial si nous voulons  que notre code s’exécute sans erreurs et gère tous les cas.


Nous allons créer une méthode qui s'occupera de remplir chaque champ. Elle commencera par situer la case, puis déterminera dans un premier temps si les différentes cases qui l'entourent existent, si c'est le cas nous contrôlons alors dans un deuxième temps si la case adjacente en question contient une bombe, puis finalement si c'est le cas nous incrémenterons notre résultat de bombe avant de contrôler la prochaine direction possible.  Une fois les huit directions contrôlées nous retournons le contenu de notre variable résultat à notre fonction main. Elle sauvegarde ainsi le nombre de bombes qui entourent la cellule concernée.

Les fonctions "minimum", "estAdjacentBombe" et "calculCellules" sont des fonctions auxiliaires à la fonction remplir champ. Elles sont documentées dans le code ci-dessous.
    /*
    *  Fonction qui situe puis controle si la case existe si c'est le cas on incremente 
une variable resultat qui retourne le nombre de bombes qui adjacente une case.
    * @ prend en parametre des int et une matrice tableau
    * @ retourn le nombre de bombes qui entourent la case
    */
    public static int remplirChamp(int ligne, int maxLigne, int collone, int maxCollone, int[][] tableau) {
        

        // variables locales 
        int resultat = 0;
        int droite, gauche, bas, haut, hautGauche, hautDroite, basGauche,basDroite;

        
        // Nous situons notre case par rapport à toutes les directions, c'est à dire combien 
        //de cases il y a à gauche , droite , haut , bas etc par rapport à la matrice.
        droite= calculCellules(collone + 1, maxCollone, true);
        gauche = calculCellules(collone + 1, maxCollone, false);
        bas = calculCellules(ligne + 1, maxLigne, true);
        haut = calculCellules(ligne + 1, maxLigne, false);

        // Nous définissons le minimum entre directions pour avoir la limite de 
        //jusqu'ou on peut aller dans la direction donnée 
        hautGauche=minimum(haut,gauche);
        hautDroite=minimum(haut,droite);
        basGauche=minimum(bas,gauche);
        basDroite=minimum(bas,droite);
        
        
        //Nous controllons qu'il y aie des cases qui existent à gauche
        if (gauche > 0  ) {
            // Nous controlons s'il y a une bombe à gauche à l'aide de la méthode  "estAdjacentBombe"
            if (estAdjacentBombe(ligne, 0, collone, -1, tableau)) {
                //Si la bombe existe nous incrémentons la valeur résultat
                resultat++;
            }

        }
        
        //Même principe que la portion de code "Gauche"
        if (droite > 0  ) {
            if (estAdjacentBombe(ligne, 0, collone, 1, tableau)) {
                resultat++;
            }

        }
        
        //Même principe que la portion de code "Gauche"
        if (bas > 0  ) {
            if (estAdjacentBombe(ligne, 1, collone, 0, tableau)) {
                resultat++;
            }

        }
        
        //Même principe que la portion de code "Gauche"
        if (haut > 0  ) {
            if (estAdjacentBombe(ligne, -1, collone, 0, tableau)) {
                resultat++;
            }

        }
        
        //Même principe que la portion de code "Gauche"
        if (hautGauche > 0  ) {
            if (estAdjacentBombe(ligne, -1, collone, -1, tableau)) {
                resultat++;
            }

        }
        
        //Même principe que la portion de code "Gauche"
        if (hautDroite > 0  ) {
            if (estAdjacentBombe(ligne, -1, collone, 1, tableau)) {
                resultat++;
            }

        }
        
        //Même principe que la portion de code "Gauche"
        if (basDroite > 0  ) {
            if (estAdjacentBombe(ligne, 1, collone, 1, tableau)) {
                resultat++;
            }

        }
        
        //Même principe que la portion de code "Gauche"
        if (basGauche > 0  ) {
            if (estAdjacentBombe(ligne, 1, collone, -1, tableau)) {
                resultat++;
            }

        }
        return resultat;

    }
    /*
    *  Fonction qui controle si la case située a
    * @ prend en parametre les coordonnées de base de la cellule, puis les coordonnées de la 
    *   cellule qu'on doit controler et finalement le tableau à deux dimentions
    * @ retourn un boolean qui indique si la case visée contient ou pas une bombe
    */
    public static boolean estAdjacentBombe(int xBase, int positionX, int yBase, int positionY, int[][] tableau) {
        boolean boolAdjacent = false;

        // Nous changeons la valeur de booladjacent à true si la case visée contient une bombe
            if(tableau[((int) (xBase + positionX))][(int) (yBase + positionY)]==bombe){
                boolAdjacent=true;
            }
            
            //Nous retournons le résultat du controle
        return boolAdjacent;

    }

    
    /*
    *  Fonction qui défini le minimum entre une variable A et une variable B
    * @ prend en parametre des valeurs entieres
    * @ retourn la valeur plus petite
    */
    public static int minimum(int a, int b) {

        int min = 0;

        if (a < b) {
            min = a;
        } else {
            min = b;
        }

        // retourne la valeur la plus petite des deux valeurs entre a et b
        return min;

    };
  
    

    /*
    *  Fonction qui retourne le nombre de collones ou lignes par rapport aux bords
    * 
    */
    public static int calculCellules(int currentCol, int nbrCol, boolean positif) {

        
        //Initialisation de la variable local
        int nbrCollones = 0;

        
        // J'entends par positif la droite pour les collones  ou le bas pour les lignes
        if (positif) {
            nbrCollones = nbrCol - currentCol;
        } else {

            nbrCollones = nbrCol - (nbrCol - currentCol);

            if (nbrCollones >= 0) {
                nbrCollones--;
            }
        }

        //Nous retournons notre valeur final
        return nbrCollones;

    }
    
};



REMPLIR TOUTES LES CELLULES

Maintenant que nous avons une méthode "remplirCellules" qui traite chaque cellule il  nous suffit de les parcourir toutes à l'aide de deux boucles for imbriquées.  Toutefois, nous devons faire attention à ne pas écraser les bombes lorsque les cases en contiennent . C'est pourquoi il est important de contrôler le contenu de la cellule avant de lui affecter un résultat.
        //REMPLISSAGE DES INDICATEURS DE BOMBES
        
        //Boucle sur les lignes
        for (int li = 0; li < maxLigne; li++) {
            
             //Boucle sur les collones
            for (int col = 0; col < maxCollone; col++) {
                
                //ON controle que la case ne contienne déja une bombe
                if (tableau[li][col] != bombe) {
                    tableau[li][col] = remplirChamp(li, maxLigne, col, maxCollone, tableau);
                }
            };
        };

AFFICHER LE RÉSULTAT FINAL

Nous voilà à la fin de notre mini-démineur. Maintenant que vous avez rempli toutes les cases il vous suffit de les parcourir en affichant l'intégral du tableau à l'aide de deux boucles imbriquées for.
Voici le code:
        //AFFICHAGE DU RESULTAT
        
        //Boucle sur les lignes
        for (int li = 0; li < maxLigne; li++) {

            //Boucles sur les collones
            for (int col = 0; col < maxCollone; col++) {

                //Nous affichons un * s'il s'agit d'une bombe
                if(tableau[li][col]==bombe){
                    System.out.print('*');
                }else{
                    System.out.print(tableau[li][col]);
                }
            };

            //Retour a la ligne lorsqu'on change de ligne
            System.out.println();
        };

        //Notre mini-démineur est poli 
        System.out.println("En vous souhaitant une bonne journée !!!");
    }// ferme la fonction main
Voilà nous espérons que ce premier atelier pratique  vous aura plu. Vous pouvez accéder à l'ensemble du code en suivant ce lien.

Voici un petit aperçu du résultat final.