Interface graphique en Java : Le Sudoku

publicité
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 permettant à 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ésolution 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 Leonhardt 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
sole.
3
Les trois classes suivantes permettent de représenter un sudoku dans la con-
a) Case
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 accesseurs 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 contient, 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 directement 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 ActionListener 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 disposition 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
5
0
0
0
8
0
0
0
1
1
0
0
0
1
0
0
0
0
0
0
0
9
2
6
0
0
0
0
0
0
1
1
1
0
0
0
2
3
0
0
0
8
7
0
0
1
1
0
0
0
1
1
0
0
0
7
0
6
0
1
0
0
0
0
1
0
1
0
1
0
0
0
0
4
0
0
0
0
6
3
0
0
1
0
0
0
0
1
1
9
0
0
0
0
0
0
0
0
1
0
0
0
0
0
0
0
0
0
0
0
3
0
0
0
1
2
0
0
0
1
0
0
0
1
1
0
0
2
0
0
7
0
0
0
0
0
1
0
0
1
0
0
0
0
3
8
0
0
6
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
0
1
1
0
0
1
0
0
0
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 fichier sur l'ordinateur, grâce à une
deuxième fenêtre qui s'ouvre (classe JFileChooser et sa méthode showOpenDialog) et affiche l'arborescence du disque
dur de l'utilisateur. La méthode ouvrirFichier() utilise naturellement le constructeur de Jeu prenant en argument un fichier, 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 (boutons, 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 appuieBouton(), 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 toujours affichées en gras. De plus, lorsqu'une ligne, une colonne ou une région est complè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 modele_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 rapidement 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 encore 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
12
}
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
@return La
*/
public int
{
return
}
de num
valeur de la case
getNum()
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)
}
13
{
}
fixe = f;
/**
*Affiche une case
*/
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append(num);
return sb.toString();
}
/**
*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
15
}
/**
*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())
{
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 incomplete
@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
import
import
import
import
java.util.*;
java.util.regex.Pattern;
java.io.File;
java.io.FileNotFoundException;
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();
}
}
}
18
/**
*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();
}
!");
jeu
19
}
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
*/
public Region getRegionDeCase(int i, int j)
{
}
/**
*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);
}
20
return (jeu[(int)(i/3)][(int)(j/3)]);
/**
*Modifieur de jeu
*/
public void setRegion(Region[][] r)
{
jeu = r;
}
/**
*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
*/
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 incomplete
@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 incomplete
@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
}
27
}
}
if( (i == 2) || (i == 5) )
sb.append("\n|-----------------------|");
if(i == 8) sb.append("\n-------------------------");
sb.append("\n");
}
return sb.toString();
import
import
import
import
import
import
java.util.*;
java.awt.*;
javax.swing.*;
java.awt.event.*;
javax.swing.event.*;
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, enregistrerSous, 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, shortcutKeyMask));
fermer.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, shortcutKeyMask));
nouveau.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, shortcutKeyMask));
nouveauAlea.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, shortcutKeyMask | java.awt.event.InputEvent.SHIFT_MASK));
effacer.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, shortcutKeyMask));
enregistrer.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, shortcutKeyMask));
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, shortcutKeyMask));
//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
les
30
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
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))
(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 verification
*/
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 !", "Felicitations", 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
35
/**
*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();
} }
/**
*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
{
trer le
37
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'enregisfichier !", "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);
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 Sudoku !", "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 enregistrer le fichier ?","Attention", JOptionPane.YES_NO_OPTION);
if(reponse == JOptionPane.YES_OPTION)
this.sauverFichier();
System.exit(0);
}
39
}
40
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();
}
Téléchargement