Interface graphique en Java : Le Sudokulabrikot.free.fr/Fac/projet/rapport.pdf · Afin de créer...

40
Pierre GUILLOT Master 1 Informatique Juillet 2006 Université du Havre RAPPORT DE PROJET : Interface graphique en Java : Le Sudoku 1

Transcript of Interface graphique en Java : Le Sudokulabrikot.free.fr/Fac/projet/rapport.pdf · Afin de créer...

Pierre GUILLOTMaster 1 InformatiqueJuillet 2006Université 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.

II. REPRESENTATION

1) Présentation des classes

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

3

Une grille de Sudoku

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.

5

Affichage d’une grille vide

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

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

Le menu Fichier

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.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

Fenêtre de choix d’un fichier

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.

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

Affichage des couleurs sur la grille

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

ANNEXESBIBLIOGRAPHIE

• 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<3;i++) { for(int j=0;j<3;j++) { region[i][j] = new Case(); } } } /** *Accesseur de region @return La case de coordonnees i,j dans la region */ public Case getCase(int i, int j) { return region[i][j]; } /** *Modifieur de region */ public void setCase(int i, int j, Case c) { (region[i][j]).setNum(c.getNum()); } /** *Accesseur de region @return La valeur de la case de coordonnees i,j dans la region */ public int getCaseNum(int i, int j) { return (region[i][j]).getNum();

14

} /** *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<3;i++) { for(int j=0;j<3;j++) { switch((region[i][j]).getNum()) {

15

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<3 && l<3) { tmp = (region[k][l]).getNum(); if(tmp != 0) { for(int i=0;i<3;i++) { for(int j=0;j<3;j++) { if( (i!=k) || (j!=l) ) if ((region[i][j]).getNum() == tmp) return false; } } } k++;l++; } return true; }

16

/** *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<3;k++) { for(int l=0;l<3;l++) { if(this.getCaseNum(k,l) == val) return false; } } return true; } /** *Affiche une region */ public String toString() { StringBuffer sb = new StringBuffer(); for(int i=0;i<3;i++) { for(int j=0;j<3;j++) { sb.append(region[i][j]+" "); } sb.append("\n"); } sb.append("\n"); return sb.toString(); } }

17

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<3;i++) { for(int j=0;j<3;j++) { jeu[i][j] = new Region(); } } }

/** *Construit la grille a partir d'un fichier */ public Jeu(File fichier) { int m = 0; jeu = new Region[3][3]; for(int i=0;i<3;i++) { for(int j=0;j<3;j++) { jeu[i][j] = new Region(); }

18

} 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<9;i++) { for(int j=0;j<9;j++) { courant = sc.nextInt(); this.setCaseNum(i,j,courant); fixe = sc.nextInt(); if(fixe == 0) b =false; else b = true; this.setCaseFixe(i,j,b); } s = sc.nextLine(); } } catch(FileNotFoundException e) { System.out.println("Erreur : le fichier n'est pas au bon emplacement !"); }

} /** *Accesseur @return La region de coordonnees i,j dans le jeu */ //retourne la region de coordonnees (i,j) public Region getRegion(int i, int j) { return jeu[i][j]; } /** *Accesseur @return La region dans laquelle se trouve la case de coordonnees i,j dans le jeu */ public Region getRegionDeCase(int i, int j)

19

{ 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<9;j++) { switch(this.getCaseNum(i,j)) { 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;

} /**

21

*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<9) { tmp = this.getCaseNum(i,j); if(tmp!=0) { for(int k=0;k<9;k++) { if(k!=j) if(tmp == this.getCaseNum(i,k)) return false; } } j++; } return true; } /** *Verifie si la ligne en cours de creation serait valide avec la valeur val, meme si elle est incomplete @return vrai ou faux */ public boolean ligneOK(int i, int j, int val) { if(val == 0) return false; for(int k=0;k<9;k++) { if(val == this.getCaseNum(i,k)) return false; } return true; }

22

/** *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<9;i++) { switch(this.getCaseNum(i,j)) { 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 colonne en cours de creation serait valide avec la valeur val, meme si elle est incomplete @return vrai ou faux */ public boolean colonneOK(int i, int j, int val) { if(val == 0) return false;

23

for(int k=0;k<9;k++) { if(val == this.getCaseNum(k,j)) return false; } return true; }

/** *Verifie si la colonne en cours de creation est valide, meme si elle est in-complete @return vrai ou faux */ public boolean colonneOK(int _i, int j) { if(this.getCaseNum(_i,j) == 0) return false; int i = 0; int tmp; while(i<9) { tmp = this.getCaseNum(i,j); if(tmp!=0) { for(int k=0;k<9;k++) { if(k!=i) if(tmp == this.getCaseNum(k,j)) return false; } } i++; } return true; } /** *Verifie si le joueur a gagne @return vrai ou faux */ public boolean gagne() {

24

for (int i=0;i<3;i++) for (int j=0;j<3;j++) if(!(jeu[i][j]).regionCompte()) return false; for (int i=0;i<9;i++) if(!colonneCompte(i) || !ligneCompte(i)) return false; return true; } /** *Remplit la grille aleatoirement */ public void remplirRandom() { Random rand = new Random( System.currentTimeMillis() ); //on parcourt la grille, on insere un chiffre entre 0 et 9 dans chaque case si c'est possible, sinon on met 0, et on fixe les cases differentes de 0 for(int i=0; i<9; i++) { for(int j=0;j<9;j++) { int v = 0; while( v <= 0 ) { v = rand.nextInt(10); if ( (this.getRegionDeCase(i,j).regionOK(i,j,v)) && (this.ligneOK(i,j,v)) && (this.colonneOK(i,j,v)) ) { this.setCaseNum(i,j,v); if(v != 0) this.setCaseFixe(i,j,true); else this.setCaseFixe(i,j,false); } else { this.setCaseNum(i,j,0); this.setCaseFixe(i,j,false); } } } } } /** *Resout le sudoku d'apres la grille donnee @return vrai ou faux */

25

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 <= 9; ++val) { if ( (this.getRegionDeCase(i,j).regionOK(i,j,val)) && (this.ligneOK(i,j,val)) && (this.colonneOK(i,j,val)) ) { this.setCaseNum(i,j,val); if (resoudre(i,j+1)) return true; } } setCaseNum(i,j,0); System.out.println(this); return false; } /** *Affiche une grille de sudoku */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append("-------------------------\n"); for(int i=0;i<9;i++) { for(int j=0;j<9;j++) { if(j == 0) sb.append("| "); sb.append(this.getCaseNum(i,j)+" "); if( (j == 2) || (j == 5) || (j == 8) ) sb.append("| ");

26

} 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<3;i++) { for(int j=0;j<3;j++) { jp[i][j] = new JPanel(); (jp[i][j]).setLayout(new GridLayout(3,3)); (jp[i][j]).setBorder(BorderFactory.createEtchedBorder()); } } //on cree les boutons et on les ajoute aux panels for(int i=0;i<9;i++) { for(int j=0;j<9;j++) { Integer in = new Integer((jeu.getCaseNum(i,j))); if(in == 0) cases[i][j] = new JButton(""); else cases[i][j] = new JButton(in.toString()); if(jeu.getCaseFixe(i,j))

30

(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<9;j++) { if((cases[i][j]).getForeground() == Color.green) { (cases[i][j]).setForeground(Color.red); this.boutonFix(i,j); } else { (cases[i][j]).setForeground(Color.blue); (cases[i][j]).setFont(new java.awt.Font("Helvetica", java.awt.Font.PLAIN, 25)); this.boutonFix(i,j); } } } /** *Decolore la ligne si elle ne contient plus tous les chiffres de 1 a 9 */ public void decoloreLigne(int i) { for (int j=0;j<9;j++)

32

{ 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<9;i++) { if((cases[i][j]).getForeground() == Color.blue) { (cases[i][j]).setForeground(Color.red); this.boutonFix(i,j); } else { (cases[i][j]).setForeground(Color.green); (cases[i][j]).setFont(new java.awt.Font("Helvetica", java.awt.Font.PLAIN, 25)); this.boutonFix(i,j); } } } /** *Decolore la colonne si elle ne contient plus tous les chiffres de 1 a 9 */ public void decoloreColonne(int j) { for (int i=0;i<9;i++) { if( ((cases[i][j]).getForeground() == Color.red) || ((cases[i][j]).getForeground() == Color.blue) ) { (cases[i][j]).setForeground(Color.blue);

33

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<9;i++) { for(int j=0;j<9;j++) { (cases[i][j]).setText(""); } } fichierCourant = null; }

34

/** *Cree une nouvelle grille aleatoire */ public void nouveauFichierAlea() { jeu.remplirRandom(); for(int i=0;i<9;i++) { for(int j=0;j<9;j++) { Integer in = new Integer((jeu.getCaseNum(i,j))); if(in == 0) (cases[i][j]).setText(""); else (cases[i][j]).setText(in.toString()); this.boutonFix(i,j); } } } /** *Ouvre un fichier enregistre sur le disque dur */ public void ouvrirFichier() { String nomFic = new String(""); JFileChooser choix = new JFileChooser(); choix.setDialogTitle("Choisir le fichier"); choix.setApproveButtonText("Ok"); //intitule du bouton int returnVal = choix.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { File fichier = choix.getSelectedFile(); Jeu jeuTmp = new Jeu(fichier); jeu = jeuTmp; for(int i=0;i<9;i++) { for(int j=0;j<9;j++) { Integer in = new Integer((jeu.getCaseNum(i,j))); if(in == 0) (cases[i][j]).setText(""); else (cases[i][j]).setText(in.toString()); this.boutonFix(i,j); } } fichierCourant = choix.getSelectedFile(); } } /**

35

*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<9;i++) { for(int j=0;j<9;j++) { fichier.write(jeu.getCaseNum(i,j)+" "); if(jeu.getCaseNum(i,j) != 0) { fichier.write("1"+" "); jeu.setCaseFixe(i,j,true); } else { fichier.write("0"+" "); jeu.setCaseFixe(i,j,false); } this.boutonFix(i,j); } fichier.write("\n"); } fichier.close(); fichierCourant = choix.getSelectedFile(); } catch(IOException e) { JOptionPane.showMessageDialog(this, "Impossible d'enregistrer le fichier !", "Dommage", JOptionPane.ERROR_MESSAGE); } } } /** *Sauvegarde le fichier courant */ public void sauverFichier()

36

{ String nomFic = new String(""); try { nomFic = fichierCourant.getAbsolutePath(); try { FileWriter fichier = new FileWriter(nomFic); for(int i=0;i<9;i++) { for(int j=0;j<9;j++) { fichier.write(jeu.getCaseNum(i,j)+" "); fichier.write(jeu.getCaseFixeInt(i,j)+" "); } fichier.write("\n"); } fichier.close(); } catch(IOException e) { JOptionPane.showMessageDialog(this, "Impossible d'enregis-trer le fichier !", "Dommage", JOptionPane.ERROR_MESSAGE); } } catch(NullPointerException e) { this.sauverFichierSous(); }

} /** *Sauvegarde la grille dans un nouveau fichier */ public void sauverFichierSous() { 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);

37

for(int i=0;i<9;i++) { for(int j=0;j<9;j++) { fichier.write(jeu.getCaseNum(i,j)+" "); fichier.write(jeu.getCaseFixeInt(i,j)+" "); } fichier.write("\n"); } fichier.close(); fichierCourant = choix.getSelectedFile(); } catch(IOException e) { JOptionPane.showMessageDialog(this, "Impossible d'enregistrer le fichier !", "Dommage", JOptionPane.ERROR_MESSAGE); } } } /** *Efface les chiffres non fixes de la grille */ public void effacer() { for(int i=0;i<9;i++) for(int j=0;j<9;j++) if(!jeu.getCaseFixe(i,j)) { jeu.setCaseNum(i,j,0); (cases[i][j]).setText(""); this.verif(i,j); } } /** *Resout le Sudoku */ public void resoudre() { if(jeu.resoudre(0,0)) { for(int i=0;i<9;i++) { for(int j=0;j<9;j++) {

38

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<9;i++) { for(int j=0;j<9;j++) { //on clique sur un bouton de la grille et on l'incremente if(e.getSource() == cases[i][j]) { this.appuieBouton(i, j); this.verif(i, j); } } } //on definit les actions des sous menus if(e.getSource() == enregistrer) this.sauverFichier(); if(e.getSource() == enregistrerSous) this.sauverFichierSous(); if(e.getSource() == ouvrir) this.ouvrirFichier(); if(e.getSource() == fermer) { int reponse = JOptionPane.showConfirmDialog(this,"Voulez vous enre-gistrer le fichier ?","Attention", JOptionPane.YES_NO_OPTION); if(reponse == JOptionPane.YES_OPTION) this.sauverFichier(); System.exit(0); }

39

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