Interface graphique en Java : Le Sudoku - Pierre GUILLOT Master 1 Informatique Juillet 2006 Université du Havre

 
CONTINUER À LIRE
Interface graphique en Java : Le Sudoku - Pierre GUILLOT Master 1 Informatique Juillet 2006 Université du Havre
Pierre GUILLOT
Master 1 Informatique
Juillet 2006
Université du Havre

                        RAPPORT DE PROJET :

       Interface graphique en Java :
                 Le Sudoku

1
INTRODUCTION :                                      3
I. HISTORIQUE DU SUDOKU                             3
II. REPRESENTATION                                  3
1) Présentation des classes                         3
a) Case                                             3
b) Région                                           4
c) Jeu                                              4
d) Explications du code et problèmes rencontrés :   4
II . L'INTERFACE GRAPHIQUE                          5
1) Présentation                                     5
a) La grille                                        5
b) Le menu                                          6
2) Réalisation                                      6
a) Graphisme                                        6
2) Le jeu                                           6
a) Lecture et écriture dans un fichier              6
b) Les actions                                      8
c) Les couleurs                                     8
d) Compilation et exécution                         9
CONCLUSION :                                        10
BIBLIOGRAPHIE                                       11
CODE                                                11

2
INTRODUCTION :

       Le sudoku est un jeu présenté sous la forme d'une grille de 81 cases, le principe
étant de la remplir selon différentes contraintes.
Le but de ce projet est de créer une interface graphique en Java pour le sudoku permet-
tant à un utilisateur de saisir une nouvelle grille et de simplifier la vision du jeu grâce à des
affichages de couleurs.
Pour réaliser tout cela, il a tout d'abord fallu créer les méthodes de contraintes et de réso-
lution d'un sudoku dans des classes représentant le jeu en mémoire, puis ensuite créer
l'interface graphique qui interagit avec ces classes.

I. HISTORIQUE DU SUDOKU

     Le sudoku a été inventé en 1979 par Howard Garns, un pigiste spécialisé dans les
puzzles, et publié cette même année pour la première fois dans Dell Magazines sous le
nom de Number Place. Après avoir été introduit au Japon, le nom devient Sudoku. En
2004, Le Times publie une première grille puis les autres journaux suivent. Depuis, le
phénomène a fait le tour du monde et est arrivé en France. Inspiré du carré latin de Leon-
hardt Euler, le but du jeu est que chaque ligne, colonne et région de 3x3 cases contienne
chaque chiffre de 1 à 9 une seule fois.

                                       Une grille de Sudoku

II. REPRESENTATION

        1) Présentation des classes

             Les trois classes suivantes permettent de représenter un sudoku dans la con-
sole.

             a) Case

3
La classe Case représente une case de la grille, elle contient donc une
valeur et un état qui définit si la case est fixe ou non. Elle contient uniquement des acces-
seurs et modifieurs qui serviront aux autres classes pour accéder à ses champs.

           b) Région

                   La classe Région représente un carré de la grille de 3x3 cases. Elle con-
tient, en plus des méthodes d'une classe canonique, des méthodes de vérification. L'une
vérifie si la région est complète, donc si elle est composée de tous les chiffres de 1 à 9, et
l'autre vérifie si une valeur peut être insérée dans cette région.

           c) Jeu

                Enfin, la classe Jeu définit une grille comme étant un carré de 3x3 régions.
C'est vraiment elle qui gère la grille. Elle contient un constructeur par défaut, ainsi qu'un
constructeur qui prend en argument un fichier, ce qui permet de charger une grille qui aura
été sauvegardée auparavant. En plus des constructeurs, accesseurs et modifieurs, elle
contient des méthodes de vérification similaires à celles de la classe Région mais qui
s'appliquent aux lignes et aux colonnes. La méthode gagne() vérifie si le joueur a gagné.
Une autre méthode permet de remplir une grille de façon aléatoire, et une de trouver la
solution d'un sudoku d'après une grille incomplète.

Toutes ces classes contiennent aussi une méthode toString() afin de pouvoir afficher les
résultats de tests de manière claire dans le terminal.

           d) Explications du code et problèmes rencontrés :

                  Un des premiers problèmes que j'ai rencontré était d'accéder à des cases
de la grille. En effet, la grille est un tableau de régions, on peut donc accéder facilement à
une case avec ses coordonnées dans la région qui la contient, mais pas dans la grille gé-
nérale. Il a alors fallu créer des méthodes permettant d'accéder à une case directement
avec ses coordonnées dans la grille. En fait la région dans laquelle se trouve la case de
coordonnées i et j se situe aux coordonnées (valeur entière de i/3) et (valeur entière de j/3)
dans la grille. Ensuite les coordonnées de la case dans cette région sont i modulo 3 et j
modulo 3. Une fois que j'ai trouvé cela, tout a été plus simple et j'ai pu faire les méthodes
de conversion qui trouve une case selon ses coordonnées dans la grille, ou dans une ré-
gion dont ont connaît les coordonnées.

       Les méthodes de résolution et de remplissage aléatoire m'ont posé aussi quelques
difficultés.
Tout d'abord pour remplir aléatoirement le jeu, j'ai créé une instance de la classe Random
qui m'a permis de générer des nombres aléatoires entre 0 et 9. On vérifie en parcourant le
tableau si la valeur aléatoire peut être insérée (grâce aux méthodes de vérification de la
classe Jeu et Région), si oui on l'insère et on fixe la case, sinon on met la case à 0 et par
conséquent la case n'est pas fixe, puisqu'elle n'a pas de valeur. Le problème de cette mé-
thode est qu'elle crée des grilles qui en apparence sont correctes, mais qui malheureuse-

4
ment ne sont pas forcément solubles. Pour cela, il faudrait utiliser en même temps la mé-
thode de résolution, mais ceci deviendrait trop lourd pour seulement créer une grille.

La méthode de résolution utilise la backtracking, c'est-à-dire qu'elle parcourt le tableau en
mettant une valeur qui convient dans la case courante, et quand ce n'est pas possible, elle
revient sur la case précédente pour y mettre une valeur différente. Lorsque le programme
arrive au bout de la grille, le sudoku est résolu, sinon il n'y a pas de solution.
L’exécution peut parfois prendre beaucoup de temps, mais le but de ce projet étant surtout
de soigner l'interface graphique, je me suis donc concentré sur l'IHM.

II . L'INTERFACE GRAPHIQUE

      L'interface permet à l'utilisateur de jouer au sudoku à partir d'une grille prédéfinie
(sauvegardée précédemment), aléatoirement créée par le programme, ou bien entrée di-
rectement par le joueur dans la grille affichée à l'écran.
Afin de créer l'interface graphique en Java, j'ai choisi d'utiliser le package Swing fourni en
standard, car il est plus récent que l'AWT, plus riche, plus optimisé et donc plus rapide.

1) Présentation

     a) La grille

          La présentation du jeu en lui même est très simple, en effet elle est constituée
de 81 boutons, décomposé en régions grâce à des bordures fines.

                                   Affichage d’une grille vide

5
b) Le menu

            Le programme dispose d'un menu Fichier qui
permet de créer une nouvelle grille vide ou aléatoire,
d'ouvrir un document et de le fermer, de sauvegarder (ou
sauvegarder sous) la grille, d' enregistrer un modèle
(c'est-à-dire créer soi-même une grille de départ et la
sauvegarder de façon à pouvoir y jouer ultérieurement),
d'effacer la grille, et de résoudre le sudoku.

2) Réalisation
                                                                             Le menu Fichier
     a) Graphisme

            La classe Sudoku est celle qui gère l'interface graphique de l'application. Elle
dérive de la classe JFrame (qui représente une fenêtre) et implémente l'interface Action-
Listener dans laquelle sont définies des méthodes permettant de gérer les événements
liés à un composant (boutons et menus dans le cas présent). Elle contient un élément de
la classe Jeu qui représentera le sudoku en mémoire, comme on l'a vu dans la première
partie. L'interface et le sudoku interagiront donc grâce à ce champ jeu.

Le constructeur de Sudoku est certainement la méthode la plus importante de la classe,
en effet, c'est là que toute l'interface est construite, et que le jeu est initialisé. Il a donc fallu
créer tous les éléments de l'interface. La fenêtre est composé de 9 panneaux (de la classe
JPanel) sur chacun desquels on a placé 9 boutons (de la classe JButton) grâce à une dis-
position sous forme de grille de 3x3 (GridLayout). Les panneaux sont insérés dans la fenê-
tre grâce à une autre GridLayout de 3x3 composants. Les boutons sont représentés par
un tableau de JButton à deux dimensions , il est ainsi facile d'accéder à un bouton précis
de la grille grâce à ses coordonnées, et de faire correspondre la valeur d'une case avec ce
bouton. En résumé, chaque région est représenté par un panneau contenant 9 boutons, et
la grille contient 9 panneaux.

Tous les sous-menus ont ensuite été initialisés avec un nom, et un raccourci clavier leur a
été attribué (raccourci qui fonctionne avec la touche de raccourci par défaut qui dépend du
système d'exploitation). Les sous-menus sont ensuite ajoutés au menu Fichier.

Le champ jeu est initialisé grâce au constructeur par défaut de la classe Jeu (qui ne prend
donc aucun argument), et toutes les cases ont alors pour valeur initiale 0. Ainsi, lorsque le
programme est lancé, la fenêtre contient une grille vide. Lorsque la case vaut 0, le bouton
est vide, sinon il contient la valeur de la case.

     2) Le jeu

           a) Lecture et écriture dans un fichier

                   Une des possibilités offertes par le programme est de créer un fichier, d'en
charger un existant et de le sauvegarder (de diverses manières). Pour lire dans un fichier,
j'ai utilisé la classe Scanner très utile et simple d'utilisation. La première étape a été

6
d'écrire un constructeur de la classe Jeu avec pour argument un fichier texte dans lequel
est écrite la grille. Dans ce fichier, chaque valeur de case est suivie par son état, 1 ou 0,
permettant de savoir si elle est fixe ou non.
Un exemple de fichier :

                      6   1   0   0   0   0   0   0   0   0   9   1   0   0   0   0   0   0
                      5   1   0   0   2   1   0   0   0   0   0   0   0   0   0   0   3   1
                      0   0   0   0   3   1   7   1   4   1   0   0   0   0   2   1   8   1
                      0   0   0   0   0   0   0   0   0   0   0   0   3   1   0   0   0   0
                      0   0   9   1   0   0   6   1   0   0   0   0   0   0   0   0   0   0
                      8   1   2   1   0   0   0   0   0   0   0   0   0   0   7   1   6   1
                      0   0   6   1   8   1   1   1   0   0   0   0   0   0   0   0   0   0
                      0   0   0   0   7   1   0   0   6   1   0   0   1   1   0   0   0   0
                      0   0   0   0   0   0   0   0   3   1   0   0   2   1   0   0   0   0

Cette grille est donc représentée ainsi dans l'interface graphique :

et affichée ainsi dans la console :
                             -------------------------
                             | 6 0 0 | 0 0 9 | 0 0 0 |
                             | 5 0 2 | 0 0 0 | 0 0 3 |
                             | 0 0 3 | 7 4 0 | 0 2 8 |
                             |-----------------------|
                             | 0 0 0 | 0 0 0 | 3 0 0 |
                             | 0 9 0 | 6 0 0 | 0 0 0 |
                             | 8 2 0 | 0 0 0 | 0 7 6 |
                             |-----------------------|
                             | 0 6 8 | 1 0 0 | 0 0 0 |
                             | 0 0 7 | 0 6 0 | 1 0 0 |
                             | 0 0 0 | 0 3 0 | 2 0 0 |
                             -------------------------

7
Les méthodes pour ouvrir un fichier,
créer un nouveau fichier, sauvegarder un
fichier, sauvegarder un fichier sous, et
sauvegarder un modèle se trouvent dans
la classe Sudoku.
Elles permettent toutes de choisir un fi-
chier sur l'ordinateur, grâce à une
deuxième fenêtre qui s'ouvre (classe JFi-
leChooser et sa méthode showOpenDia-
log) et affiche l'arborescence du disque
dur de l'utilisateur. La méthode ouvrirFi-
chier() utilise naturellement le construc-
teur de Jeu prenant en argument un fi-
chier, et crée donc une grille à partir du
fichier choisi.                                                 Fenêtre de choix d’un fichier
Les méthodes d'écriture dans un fichier
se servent toutes de la classe FileWriter
et de sa méthode write().
sauverFichierSous() crée un nouveau fichier et y écrit la grille représentée dans la fenêtre
du jeu.
sauverFichier() enregistre le fichier courant (que l'on a ouvert, ou “enregistré sous…” pré-
cédemment)
sauverModele() enregistre la grille dans un nouveau fichier en écrivant tous les chiffres
entrés par l'utilisateur, et en initialisant leur état à fixe (1 dans le fichier).

     b) Les actions

                  Pour entrer un chiffre dans une case, je me suis demandé quelle était la
meilleure solution. Celle qui m'a semblé la plus pratique est celle qui consiste à ce que la
valeur de la case augmente de 1 lorsque l'utilisateur clique sur le bouton correspondant.
Par conséquent, si le joueur appuie sur un bouton qui est égal à 3, la valeur deviendra 4.
Pour cela, j'ai créé la méthode appuieBouton() qui prend en argument les coordonnées du
bouton, elle modifie la valeur de la case elle-même puis modifie l'affichage du bouton. En
plus de cette méthode, ce qui permet de gérer les événements sur des composants (bou-
tons, sous-menus) est la méthode actionPerformed() de l'interface ActionListener. Quand
on clique sur un bouton, actionPerformed() est exécutée, puis elle appelle la méthode ap-
puieBouton(), ce qui change sa valeur. actionPerformed() gère aussi les sous-menus en
appelant les méthodes correspondantes à l'action désirée par l'utilisateur, notamment les
méthodes de chargement et de sauvegarde des fichiers, ou bien la résolution du sudoku.

     c) Les couleurs

            Dans la souci d'aider le joueur dans sa vision du jeu, les cases fixes sont tou-
jours affichées en gras. De plus, lorsqu'une ligne, une colonne ou une région est com-
plète, on l'affiche d'une couleur différente. Une ligne complète sera colorée en bleu, une
colonne complète en vert, l'intersection des deux sera en rouge, et une région complète
sera encadrée par une bordure rouge. La méthode verif() vérifie si une ligne, une colonne

8
ou une région est complète à chaque clic sur un bouton, puis selon le cas, elle appelle la
méthode de coloration (ou de décoloration) correspondante. verif() se sert des méthodes
de vérification de la classe Jeu présentées auparavant.
Les méthodes de coloration agissent uniquement sur le texte des boutons (au moyen de
la méthode setForeground()), ce qui permet d'avoir une vision claire, et de ne pas trop
charger l'interface graphique. Une région complète aura simplement une bordure rouge.
Lorsque l'utilisateur a tout rempli correctement et a donc gagné, une fenêtre s'ouvre et le
prévient que la partie est finie grâce à la méthode gagne() qui est aussi appelée à chaque
fois que l'on appuie sur un bouton.

                                Affichage des couleurs sur la grille

     d) Compilation et exécution

             Afin de compiler toutes les classes et de créer la javadoc j'ai utilisé un makefile.
Il suffit d'exécuter la commande make dans le répertoire où se trouvent les classes, elles
seront ainsi compilées, on exécute alors le programme avec Java Sudoku. La javadoc se
créera dans le répertoire doc/ grâce à la commande make javadoc. Il suffit alors d'ouvrir
doc/index.html pour consulter la documentation. La commande make jar crée une archive
java Sudoku.jar, et on exécute le programme avec java -jar Sudoku.jar. Le fichier mode-
le_sudoku est une grille prédéfinie dont on peut se servir pour tester le programme.

9
CONCLUSION :

      Le but de ce projet était de faire une interface graphique simple et efficace pour le
Sudoku, et d'aider le joueur grâce à différentes colorations de la grille selon le remplissage
des lignes, des colonnes et des régions.
Personnellement, ce projet m'a apporté beaucoup de nouvelles connaissances en ce qui
concerne les interfaces graphiques en Java que je ne maîtrisais pas vraiment.
Le programme pourrait être amélioré en ce qui concerne la méthode de résolution, ou la
méthode de génération de grilles aléatoires. Pour contourner ce problème, il pourrait être
intéressant d'avoir des grilles préenregistrée avec leur solution par exemple, comme cela,
les grilles seraient chargées vite, et si le joueur voulait la solution il pourrait y accéder ra-
pidement aussi.
Néanmoins, le programme respecte toutes les contraintes originales, même si quelques
détails de l'interface graphique (que ce soit la présentation générale, les couleurs, ou en-
core les menus) pourraient être modifier avec un but précis.

10
ANNEXES
BIBLIOGRAPHIE

• http://fr.wikipedia.org/wiki/Sudoku

• http://www.planete-sudoku.com/

• http://sudoku.koalog.com/php/sudoku_fr.php

• http://java.sun.com/j2se/1.5.0/docs/api/index.html

CODE

/**
 *Classe Case, represente une case du Sudoku
 *@author Pierre Guillot
 */

public class Case
{
    /**
    *valeur de la case
    */
    public int num;
    /**
    *etat de la case
    */
    public boolean fixe;

     /**
     *Constructeur par defaut, cree une case vide, et non fixe
     */
     public Case()
      {
         num = 0;
         fixe = false;
      }

     /**
     *Constructeur, cree une case dont la valeur est n, et non fixe
     */
     public Case(int n)
      {
          num = n;

11
fixe = false;
     }

     /**
     *Constructeur, cree une case dont la valeur est n, dont l'etat est f
     */
     public Case(int n, boolean f)
      {
          num = n;
          fixe = f;
     }

     /**
     *Constructeur, cree une case vide, dont l'etat est f
     */
     public Case(boolean f)
      {
         num = 0;
         fixe = f;
      }

     /**
     *Accesseur   de num
     @return La   valeur de la case
     */
     public int   getNum()
      {
         return   num;
      }

     /**
     *Modifieur de num
     */
     public void setNum(int n)
      {
          num = n;
      }

     /**
     *Accesseur de fixe
     @return L'etat de la case
     */
     public boolean getFixe()
      {
         return fixe;
      }

     /**
     *Modifieur de fixe
     */
     public void setFixe(boolean f)

12
{
          fixe = f;
     }

     /**
     *Affiche une case
     */
     public String toString()
      {
         StringBuffer sb = new StringBuffer();
          sb.append(num);
         return sb.toString();
      }
}

13
/**
 *Classe Region, represente une region du Sudoku
 *@author Pierre Guillot
 */

public class Region
{
    /**
    *Tableau de 3x3 Case
    */
    public Case[][] region;

     /**
     *Constructeur par defaut : construit une region avec que des cases vides
     */
     public Region()
      {
         region = new Case[3][3];
         for(int i=0;i
}

     /**
     *Modifieur de region
     */
     public void setCaseNum(int i, int j, int c)
      {
          (region[i][j]).setNum(c);
      }

     /**
     *Accesseur de fixe
     @return L'etat de la case de coordonnees i,j dans la region
     */
     public boolean getCaseFixe(int i, int j)
      {
         return (region[i][j]).getFixe();
      }

     /**
     *Modifieur de fixe
     */
     public void setCaseFixe(int i, int j, boolean f)
      {
          (region[i][j]).setFixe(f);
      }

     /**
     *Verifie si la region contient tous les chiffres de 1 a 9 une fois chacun
     @return vrai ou faux
     */
     public boolean regionCompte()
      {
         boolean un = false;
         boolean deux = false;
         boolean trois = false;
         boolean quatre = false;
         boolean cinq = false;
         boolean six = false;
         boolean sept = false;
         boolean huit = false;
         boolean neuf = false;

         for(int i=0;i
case   1   :   un = true; break;
                        case   2   :   deux = true; break;
                        case   3   :   trois = true; break;
                        case   4   :   quatre = true; break;
                        case   5   :   cinq = true; break;
                        case   6   :   six = true; break;
                        case   7   :   sept = true; break;
                        case   8   :   huit = true; break;
                        case   9   :   neuf = true; break;
                    }
               }
          }

         if(un && deux && trois && quatre && cinq && six && sept && huit && neuf)
             return true;
         else return false;

     }

    /**
    *Verifie si la region en cours de creation est valide meme si elle est in-
complete
    @return vrai ou faux
    */
    public boolean regionOK(int _i, int _j)
     {
         if(this.getCaseNum(_i,_j) == 0)
             return false;

         int k=0;
         int l=0;
         int tmp;
         while(k
/**
    *Verifie si la region en cours de creation serait valide avec la valeur val,
meme si elle est incomplete
    @return vrai ou faux
    */
    public boolean regionOK(int i, int j, int val)
     {
        if(val == 0)
            return false;

         for(int k=0;k
import   java.util.*;
import   java.util.regex.Pattern;
import   java.io.File;
import   java.io.FileNotFoundException;
import   java.util.Scanner;

/**
  *Classe Jeu, represente le Sudoku
  *@author Pierre Guillot
  */
public class Jeu
{
     /**
     *Tableau de 3x3 Region
     */
     public Region[][] jeu;

     /**
     *Constructeur par defaut, construit une grille vide
     */
     public Jeu()
      {
         int m = 0;
         jeu = new Region[3][3];
         for(int i=0;i
}

           try
            {
                   //on parcourt le fichier avec un scanner qui lit dans le fichier
                   Scanner sc = new Scanner(fichier);
                   //sc.useDelimiter(Pattern.compile(" "));
                     String s;
                   int courant;
                   int fixe;
                   boolean b;
                   for(int i=0;i
{
         return (jeu[(int)(i/3)][(int)(j/3)]);
     }

     /**
     *Modifieur de jeu
     */
     public void setRegion(Region[][] r)
      {
         jeu = r;
      }

     /**
     *Accesseur
     @return La valeur de la case de coordonnees i,j dans le jeu
     */
     public int getCaseNum(int i, int j)
      {
         return (jeu[(int)(i/3)][(int)(j/3)]).getCaseNum(i%3,j%3);
      }

     /**
     *Modifieur de num de la case de coordonneesi,j dans la grille
     */
     public void setCaseNum(int i, int j, int c)
      {
         (jeu[(int)(i/3)][(int)(j/3)]).setCaseNum(i%3,j%3,c);
      }

     /**
     *Accesseur
     @return L'etat de la case de coordonnees i,j dans le jeu
     */
     public boolean getCaseFixe(int i, int j)
      {
         return (jeu[(int)(i/3)][(int)(j/3)]).getCaseFixe(i%3,j%3);
      }

     /**
     *Modifieur de fixe de la case de coordonneesi,j dans la grille
     */
     public void setCaseFixe(int i, int j, boolean f)
      {
         (jeu[(int)(i/3)][(int)(j/3)]).setCaseFixe(i%3,j%3,f);
      }

     /**
     *Accesseur
     @return L'etat (0 ou 1)de la case de coordonnees i,j dans le jeu

20
*/
     public int getCaseFixeInt(int i, int j)
      {
         if( (jeu[(int)(i/3)][(int)(j/3)]).getCaseFixe(i%3,j%3) )
             return 1;
         else return 0;
      }

     /**
     *Verifie si la ligne contient tous les chiffres de 1 a 9 une fois chacun
     @return vrai ou faux
     */
     public boolean ligneCompte(int i)
      {
         boolean un = false;
         boolean deux = false;
         boolean trois = false;
         boolean quatre = false;
         boolean cinq = false;
         boolean six = false;
         boolean sept = false;
         boolean huit = false;
         boolean neuf = false;

           for(int j=0;j
*Verifie si la ligne en cours de creation est valide, meme si elle est in-
complete
    @return vrai ou faux
    */
    public boolean ligneOK(int i, int _j)
     {
         if(this.getCaseNum(i, _j) == 0)
             return false;

         int j = 0;
         int tmp;

         while(j
/**
     *Verifie si la ligne contient tous les chiffres de 1 a 9 une fois chacun
     @return vrai ou faux
     */
     public boolean colonneCompte(int j)
      {
         boolean un = false;
         boolean deux = false;
         boolean trois = false;
         boolean quatre = false;
         boolean cinq = false;
         boolean six = false;
         boolean sept = false;
         boolean huit = false;
         boolean neuf = false;

         for(int i=0;i
for(int k=0;k
for (int i=0;i
public boolean resoudre(int i, int j)
      {
         //quand on arrive au bout de la ligne on va a la ligne suivante
         if (j == 9)
          {
             j = 0;
             if (++i == 9)
                 return true;
          }

         //si la case est fixe, on regarde la suivante
         if (this.getCaseFixe(i,j))
                 return resoudre(i,j+1);

        //on regarde si on peut placer une valeur, si oui on passe a la case
suivante, si non on revient sur nos pas
        for (int val = 1; val
}
            if( (i == 2) || (i == 5) )
                sb.append("\n|-----------------------|");
            if(i == 8) sb.append("\n-------------------------");
            sb.append("\n");

          }
         return sb.toString();
     }

}

27
import   java.util.*;
import   java.awt.*;
import   javax.swing.*;
import   java.awt.event.*;
import   javax.swing.event.*;
import   java.io.*;

/**
 *Classe Sudoku, represente un Sudoku dans une fenetre
 *@author Pierre Guillot
 */

public class Sudoku extends JFrame implements ActionListener
{
    /**
    *Objet de la classe Jeu, represente une grille de Sudoku
    */
    Jeu jeu;
    /**
    *Fichier courant sur lequel l'utilisateur joue
    */
    File fichierCourant;
    Container c;
    JPanel panel, panelGeneral, panelHaut;
    JPanel[][] jp = new JPanel[3][3];
    /**
    *Boutons representant les cases
    */
    JButton cases[][] = new JButton[9][9];
     GridLayout grille;
    JMenu menuFichier;
     JMenuBar menu;
    JMenuItem enregistrer, fermer, nouveau, nouveauAlea, ouvrir, enregistrer-
Sous, enregistrerModele, effacer, resoudre;

    /**
    *Constructeur par defaut, cree la grille, puis cree une fenetre en y plaçant
tous les elements, et l'affiche
    */
    public Sudoku()
     {
        //on cree la fenetre
        super("Sudoku");

         //on cree le jeu
         jeu = new Jeu();

         //on definit la terminaison du programme lorsqu'on ferme la fenetre
         this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

         //definit la taille de la fenetre

28
setSize(650,650);

       //On ajoute la barre des menus
       menu = new JMenuBar();
       menuFichier = new JMenu("Fichier");
       ouvrir = new JMenuItem("Ouvrir");
       fermer = new JMenuItem("Fermer");
       nouveau = new JMenuItem("Nouvelle grille");
       effacer = new JMenuItem("Effacer la grille");
       fermer = new JMenuItem("Fermer");
       nouveau = new JMenuItem("Nouvelle grille");
       nouveauAlea = new JMenuItem("Nouvelle grille aleatoire");
       enregistrer = new JMenuItem("Enregistrer");
       enregistrerSous = new JMenuItem("Enregistrer sous...");
       enregistrerModele = new JMenuItem("Enregistrer comme modele");
       resoudre = new JMenuItem("Resoudre ce Sudoku");

        //on definit les raccourcis clavier
        int shortcutKeyMask =
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
        ouvrir.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, shortcutKey-
Mask));
        fermer.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, shortcutKey-
Mask));
        nouveau.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, shortcutK-
eyMask));
        nouveauAlea.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, short-
cutKeyMask | java.awt.event.InputEvent.SHIFT_MASK));
        effacer.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, shortcutK-
eyMask));
        enregistrer.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, short-
cutKeyMask));
        enregistrerSous.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
shortcutKeyMask | java.awt.event.InputEvent.SHIFT_MASK));
        enregistrerModele.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
shortcutKeyMask | java.awt.event.InputEvent.ALT_MASK));
        resoudre.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, shortcutK-
eyMask));
        //on ajoute les elements au menu
          menuFichier.add(nouveau);
        menuFichier.add(nouveauAlea);
        menuFichier.addSeparator();
        menuFichier.add(ouvrir);
        menuFichier.add(fermer);
        menuFichier.addSeparator();
        menuFichier.add(effacer);
        menuFichier.addSeparator();
        menuFichier.add(enregistrer);
        menuFichier.add(enregistrerSous);
        menuFichier.add(enregistrerModele);
        menuFichier.addSeparator();

29
menuFichier.add(resoudre);
       menu.add(menuFichier);

       //On ajoute des ecouteurs aux elements du menu
       ouvrir.addActionListener(this);
       nouveau.addActionListener(this);
       nouveauAlea.addActionListener(this);
       fermer.addActionListener(this);
       effacer.addActionListener(this);
       enregistrer.addActionListener(this);
       enregistrerSous.addActionListener(this);
       enregistrerModele.addActionListener(this);
       resoudre.addActionListener(this);
        setJMenuBar(menu);

        //on cree un conteneur et un panel avec une GridLayout puis on ajoute
les boutons
         c = getContentPane();
        panel = new JPanel();
        grille = new GridLayout(3,3);
         panel.setLayout(grille);
        panelGeneral = new JPanel();
        panelGeneral.setLayout(new BorderLayout());
        panelGeneral.add(panel, BorderLayout.CENTER);
        effacer.addActionListener(this);

       //on cree les panels qui representeront les 9 regions de 3x3 cases
       for(int i=0;i
(cases[i][j]).setFont(new java.awt.Font("Helvetica",
java.awt.Font.BOLD, 25));
                else (cases[i][j]).setFont(new java.awt.Font("Helvetica",
java.awt.Font.PLAIN, 25));
                (cases[i][j]).setSize(10,10);
                (jp[(int)(i/3)][(int)(j/3)]).add(cases[i][j]);
                panel.add(jp[(int)(i/3)][(int)(j/3)]);
                 //on ajoute des ecouteurs aux boutons
                (cases[i][j]).addActionListener(this);
              }

          }

         c.add(panelGeneral);

         //on affiche la fenetre
          show();
     }

    /**
    *Affiche un bouton fixe en gras
    */
    public void boutonFix(int i,int j)
     {
        if(jeu.getCaseFixe(i,j))
         {
            (cases[i][j]).setFont(new java.awt.Font("Helvetica",
java.awt.Font.BOLD, 25));
         }
     }

    /**
    *Colore les colonnes les lignes et les regions (ou les decolore) apres veri-
fication
    */
    public void verif(int i, int j)
     {
         this.boutonFix(i,j);

         this.coloreRegion(i,j);
         if (jeu.ligneCompte(i))
             coloreLigne(i);
         else decoloreLigne(i);

         if (jeu.colonneCompte(j))
             coloreColonne(j);
         else decoloreColonne(j);

31
if (jeu.gagne())
            JOptionPane.showMessageDialog(this, "Vous avez gagne !", "Felicita-
tions", JOptionPane.PLAIN_MESSAGE);
     }

    /**
    *Colore la region si elle contient tous les chiffres de 1 a 9
    */
    public void coloreRegion(int i, int j)
     {
        //on verifie si la region de la case sur laquelle on vient de cliquer
est valide, si oui, on la colorie
        if( (jeu.getRegionDeCase(i,j)).regionCompte() )

(jp[(int)(i/3)][(int)(j/3)]).setBorder(BorderFactory.createLineBorder(Color.red)
);
        else
(jp[(int)(i/3)][(int)(j/3)]).setBorder(BorderFactory.createEtchedBorder());
     }

    /**
    *Colore la ligne si elle contient tous les chiffres de 1 a 9
    */
    public void coloreLigne(int i)
     {
        for (int j=0;j
{
            if( ((cases[i][j]).getForeground() == Color.red) ||
((cases[i][j]).getForeground() == Color.green) )
              {
                 (cases[i][j]).setForeground(Color.green);
                 this.boutonFix(i,j);
              }
            else
              {
                 (cases[i][j]).setForeground(Color.black);
                 (cases[i][j]).setFont(new java.awt.Font("Helvetica",
java.awt.Font.PLAIN, 25));
                 this.boutonFix(i,j);
              }
        }
     }

    /**
    *Colore la colonene si elle contient tous les chiffres de 1 a 9
    */
    public void coloreColonne(int j)
     {
        for (int i=0;i
this.boutonFix(i,j);
                }
              else
                {
                (cases[i][j]).setForeground(Color.black);
                (cases[i][j]).setFont(new java.awt.Font("Helvetica",
java.awt.Font.PLAIN, 25));
                this.boutonFix(i,j);
              }
        }
     }

     /**
     *Incremente le chiffre du bouton quand on appuie dessus
     */
     public void appuieBouton(int i, int j)
      {
         if(!jeu.getCaseFixe(i,j))
          {
             if(jeu.getCaseNum(i,j) < 9)
               {
                 jeu.setCaseNum(i,j,jeu.getCaseNum(i,j) + 1);
               }

              else jeu.setCaseNum(i, j, 0);

              Integer in = new Integer(jeu.getCaseNum(i,j));
              if (in == 0) (cases[i][j]).setText("");
              else (cases[i][j]).setText(in.toString());
          }
     }

     /**
     *Cree une nouvelle grille vide
     */
     public void nouveauFichier()
      {
         Jeu tmp = new Jeu();
          jeu = tmp;
         for(int i=0;i
/**
     *Cree une nouvelle grille aleatoire
     */
     public void nouveauFichierAlea()
      {
         jeu.remplirRandom();
         for(int i=0;i
*Enregistre un modele de grille, toutes les cases avec une valeur sont fixes
     */
     public void sauverModele()
      {
         String nomFic = new String("");
         JFileChooser choix = new JFileChooser();
         int returnVal = choix.showSaveDialog(this);
         if (returnVal == JFileChooser.APPROVE_OPTION)
          {
             nomFic = choix.getSelectedFile().getAbsolutePath();
             try
               {
                 FileWriter fichier = new FileWriter(nomFic);

                   for(int i=0;i
{
             String nomFic = new String("");
             try
               {
                 nomFic = fichierCourant.getAbsolutePath();
                 try
                    {
                      FileWriter fichier = new FileWriter(nomFic);

                        for(int i=0;i
for(int i=0;i
Integer in = new Integer((jeu.getCaseNum(i,j)));
                            (cases[i][j]).setText(in.toString());
                            this.boutonFix(i,j);
                        }
                  }

          }

        else JOptionPane.showMessageDialog(this, "Impossible de resoudre ce Su-
doku !", "Impossible", JOptionPane.ERROR_MESSAGE);

      }

     /**
     *Gere les actions des boutons et des menus
     */
     public void actionPerformed(ActionEvent e)
      {

          for(int i=0;i
if(e.getSource() == effacer)
             this.effacer();

         if(e.getSource() == nouveau)
             this.nouveauFichier();

         if(e.getSource() == nouveauAlea)
             this.nouveauFichierAlea();

         if(e.getSource() == enregistrerModele)
             this.sauverModele();

         if(e.getSource() == resoudre)
             this.resoudre();

     }

     /**
     *Cree un nouveau Sudoku
     */
     public static void main(String[] args)
      {
         //place la barre des menus ou il faut sous Mac OS
         System.setProperty("apple.laf.useScreenMenuBar","true");

         Sudoku fenetre = new Sudoku();
     }
}

40
Vous pouvez aussi lire