Vous êtes sur la page 1sur 18

Exercice de Programmation Objet en Java

Septembre 2011

Univ. Paris 11 - Licence 5ème semestre - 2010/2011


Parcours Informatique, MIAGE et BIBS

Mise à Niveau

Table des matières


I Mise en forme 2

II Héritage et classe abstraite 6

III Interfaces, généricité 8

IV Exception, Architecture pour un jeu et entrées/sorties 11

V Interfaces Graphiques 14

VI Dessin et Evénements : le morpion - suite 17

1
Rappel : il est fortement conseillé de consulter la documentation en ligne sur http ://java.sun.com/.

I Mise en forme
Exercice 1.
Prise en main d’Eclipse
1. Lancer Eclipse.
2. Sélection du répertoire de travail
Dans la 1re fenêtre, sélectionner un répertoire. Ce dossier sera votre répertoire de travail où seront
stockés les sources (.java) et les fichiers compilés (.class) de tous vos programmes.
3. Premier projet
Créer un nouveau projet (File ¿ New ¿ Project).
1re fenêtre : Select a wizard, sélectionner Java project
2e fenêtre : donner un nom au projet. Vérifier que la version de java utilisée est bien la version 1.5
(partie JRE ) puis Finish
4. Première classe
Dans la partie de droite (Package Explorer), sélectionner le projet puis créer une nouvelle classe
(clic droit, New ¿ Class)
Donner un nom à la classe : Factorielle et vérifier que la case public static void main (String[]
args) est bien cochée.
Votre première classe est créée !

Exercice 2.
Un premier programme
1. Compléter la classe Factorielle que vous venez de créer : Factorielle.java
2. Sauver cette classe, et regarder dans l’onglet Problems en bas. Eclipse permet de compiler le code  à
la volée  (c.-à-d. au fur et à mesure que le code est saisi) et affiche les résultats de la compilation
dans cet onglet. Il est possible, en faisant un clic droit sur un message d’erreur, d’obtenir plus
d’informations sur celle-ci et, notamment, des suggestions pour la corriger.
3. Corriger ce programme pour obtenir l’affichage correct (c’est-à-dire : factorielle de 5 : 120 )
4. Pour faire tourner le programme : clic droit sur Factorielle dans le Package Explorer puis : Run
as ¿ Java application

Exercice 3. Quelques petits rappels


1. Dans le morceau de code suivant, donnez les valeurs des variables après chaque instruction.
int e1 = 3;
int e2 = e1++;
int e3 = ++e1;
e1 = e1++;
e3++;

2. Quel problème se pose dans le morceau de code suivant ? Résolvez-le.

2
short x = 5;
short y = 15;
x = x + y;

3. Qu’affiche le morceau de code suivant sur la sortie standard ?


byte a = (byte) 10;
byte b = (byte) 120;
a = (byte) (a+b);
System.out.println(’’a=’’ + a);

4. Le programme suivant comporte plusieurs erreurs. Lesquelles ? Corrigez-les. Qu’affiche ce pro-


gramme ?
class Machin {
int truc;

public static void main (String[] args) {


for (int i=0;i<10;i++)
truc = truc + 10;
System.out.println(i);
}
}

Exercice 4. Les tableaux et la ligne de commande


Dans cet exercice, on considère des tableaux d’entiers.
1. En java comment sont passés les arguments de la ligne de commande ?
2. Créer une classe TableauDEntier, y ajouter une méthode static qui servira à créer un tableau d’entier
dont les valeurs seront lues sur la ligne de commandes. Tester votre classe en écrivant un main.
3. Ecrire une méthode public static int elementMax(int[] tab) retournant l’élément maximal du tableau
passé en argument. La tester.
4. Modifier votre main pour avoir une application qui lit un tableau d’entier sur la ligne de commande
et qui en affiche le maximum.

Exercice 5.
Intérêt des piles
On souhaite écrire un programme qui teste si les délimiteurs présents dans une chaine sont bien
utilisés.
Les délimiteurs que l’on considère sont : {}, [] et (). Une chaine est bien formée si chaque délimiteur
 ouvrant  est fermé par son délimteur  fermant . Par exemple ( doit être fermé par ). Il faut

également que les délimiteurs soient fermés  dans l’ordre  : les délimiteurs ouverts en premier doivent
être fermés en dernier. Ces règles sont identiques à celle utilisées dans un programme Java.
Par exemple, les expressions suivantes sont correctement formées :

c[d]
a{b[c]d}e
mais pas celles-ci :
a{b(c]d}e ( fermé par ]
a[b{c}d]e} } n’est pas ouvert
a{b(c) { n’est pas fermé

3
1. Écrivez une méthode non-récursive qui permet de tester si une expression donnée est correctement
formée ou non. Cette méthode s’appelera check et devra être définie dans une classe nommée
Parsing.
Remarque : La bibliothèque standard de Java définit une classe Stack qui implémente une pile. Pour
l’utiliser, il suffit :
– d’inclure la bibliothèque en ajoutant au début du programme la ligne : import java.util.Stack ;
– de créer une instance : Stack¡Type¿ s = new Stack¡Type¿() ; où Type est le type des données que
l’on souhaite stocker dans la pile 1 .
– de manipuler la pile à l’aide des méthodes push, pop et isEmpty.

Exercice 6.
Vérification d’adresse IP Une adresse ip est définie par quatre nombres compris entre 0 et 255, séparés
par des points. Par exemple : 212.85.150.134. L’objectif de cet exercice est de vérifier si une chaı̂ne de
caractères donnée définie bien une adresse ip. Pour cela il faut vérifier que :
– elle comporte 3 points ;
– elle commence et se termine par un nombre (.123. n’est pas une adresse ip) ;
– les caractères situés entre les points sont des chiffres compris entre 0 et 255.
Compléter le code se trouvant sur le site du cours pour que celui-ci affiche ”ip valide” quand la chaine
ip est une adresse ip valdie et ”ip non valide” dans le cas contraire.
Les exemples suivant permettent de s’assurer que le code traite bien tous les cas :
”127.0.0.1” ”127.231.1.1”
”1.2.3.4” ”12.2.3”
”12.3.213.123.123” ”1231.12.2.3”
”.1.2.3” ”1.2.3.”
”1.2..3”
On pourra utiliser les instructions suivantes :
– int Integer.parseInt(String s) pour convertir une chaı̂ne de caractères en nombre (si et seulement si
la chaı̂ne de caractères correspond à un nombre) ;
– bool isNumeric(String s) pour vérifier si chaı̂ne de caractère représente un nombre (ce code sera
expliqué lors du cours prochain) :
– la méthode split de la classe String dont le fonctionnement est décrit ci-dessous :
public class ExempleSplit {

public static void main(String[] a) {


String s = "Bonjour.les.amis";
String separateur = "\\.";

String[] mot = s.split(separateur);


for (String m : mot) {
System.out.println(m);
}
}

}
La sortie du programme précédent est :
1. Stack est un type paramétré, notion spécifique à Java que nous détaillerons à la fin du semestre.

4
Bonjour
les
amis

Exercice 7. Accès aux attributs


Nous disposons de la classe Point que voici :
public class Point {
public double x,y; // attributs de la classe

public Point(double abscisse, double ordonnee) { // le constructeur


x = abscisse;
y = ordonnee;
}
public static double distance(Point a, Point b) { // calcule la distance du point a au point b
double xdiff, ydiff;
xdiff = a.x - b.x;
ydiff = a.y - b.y;
return Math.sqrt(xdiff*xdiff + ydiff*ydiff); // Math.sqrt retourne un double
}
public double distance(Point a) { // calcule la distance du point receveur au point a
double xdiff, ydiff;
xdiff = x - a.x;
ydiff = y - a.y;
return Math.sqrt(xdiff*xdiff + ydiff*ydiff);
}
public static double alea(double inf, double sup) { // rend un nombre compris entre inf et sup
double r= Math.random(); // Math.random retourne un double
return(inf + r*(sup - inf));
}
public void affiche() { // affiche le point
System.out.println("l’abscisse est " + x);
System.out.println("l’ordonnee est " + y);
}
public static void main(String[] args) {
Point a1 = new Point(alea(0.0,2.0),alea(-1.0,0.0));
a1.affiche();
Point a2 = new Point(alea(-1.0,1.0),alea(-2.0,1.0));
a2.affiche();
Point a3 = new Point(alea(0.0,-1.0),alea(-9.0,-8.0));
a3.affiche();
System.out.println(Point.distance(a1,a2));
System.out.println(a1.distance(a2));
}
}

1. Critiquer la conception de cette classe. Corrigez-la.

2. De plus, n’y-a-t-il pas redondance dans le code des méthodes distance. Quelle solution proposez-
vous ?
3. Que doit-on faire pour pouvoir tester l’égalité stricte entre deux points ?
4. Comment faire pour introduire la notion de précision dans l’égalité entre deux points ? Comment
s’assurer que la précision soit la même pour tous les points ?
5. Un lecteur impertinent pourrait faire remarquer, qu’il existe déjà une classe Point dans l’API
standard. Comment être sûr de laquelle est utilisée ?

Exercice 8. Nombre complexe


Partie I. (partie 2, la semaine prochaine)

5
On souhaite définir une classe Complexe pour manipuler facilement des nombres complexes.
1. Définir une classe Complexe comprenant deux champs de type double, qui représentent respective-
ment la partie réelle et la partie imaginaire d’un nombre complexe.
2. Définir un constructeur qui permette de construire un nombre complexe :
public Complexe(double r, double i) {
...
}
3. Écrire la méthode toString qui affiche un nombre complexe sous la forme : a + i * b.
public String toString() {
...
}
4. Créer un nouveau fichier Test.java. Tester le constructeur et la méthode d’affichage depuis la
méthode main de ce fichier, en construisant différents nombres (penser à tester les cas particu-
liers).
5. Définir des fonctions pour l’addition et la multiplication de deux nombres complexes. Ces deux
fonctions doivent prendre en argument un Complexe et doivent retourner un nouveau Complexe qui
est le résultat de l’opération sur le nombre argument et le nombre sur lequel est appelée l’opération.
public Complexe ajouter(Complexe c) {
...
}
public Complexe multiplier(Complexe c) {
...
}
6. Comment tester l’égalité entre deux complexes ? Ecrire la méthode appropriée.

II Héritage et classe abstraite


Exercice 9. Cherchez l’erreur
Récupérer la classe Cercle dans le jar associé au TD.
1. Compilez puis exécutez cette classe. Quel résultat obtenez-vous ?
2. Y-aurait-il une erreur de programmation ? si oui, proposez une correction.

Exercice 10. Nombre complexe, partie 2


La classe Complexe est désormais donnée (voir sur le site web du module) sous forme d’un fichier com-
pilé dans l’archive complexe.jar. L’objectif est de réutiliser cette classe et d’augmenter ces fonctionnalités
pour accéder à un nombre complexe également via son module et son argument.
1. Quelle est l’interface de la classe Complexe ?
2. Créer une classe MonComplexe, en particulier : définir son constructeur ; définir les méthodes per-
mettant d’accéder et de modifier le module et l’argument du nombre complexe ; surcharger la
fonction d’affichage.
3. Tester la classe MonComplexe dans différents cas.

6
Exercice 11. Les formes géométriques
L’objectif est de réaliser un éditeur rudimentaire de dessins à base de formes géométriques. On souhaite
disposer d’un package formesgéométriques regroupant les classes permettant de manipuler des formes
géométriques bidimensionnelles simples. Nous ne traiterons ici que les formes de types : Cercle, Rectangle,
et Carré.
Etant donnée, l’application visée, les contraintes suivantes sont également à satisfaire :
– Chaque forme possède un centre de gravité qui est une instance de la classe java.awt.Point ainsi
qu’une couleur qui est une instance de la classe java.awt.Color. Ces deux classes sont à réutiliser
via une composition.
– Un objet de type Cercle est caractérisé par son centre de gravité et son rayon.
– Un objet de type Rectangle est caractérisé par son centre de gravité, sa largeur et sa hauteur.
– Toute forme géométrique doit posséder les méthodes suivantes :
– translation, prenant en paramètre deux nombres exprimant le déplacement horizontal et vertical.
– homothetie, prenant en paramètre un nombre représentant le ratio de l’homothétie.
– Toute forme est capable de donner sa représentation sous la forme d’une chaı̂ne de caractères
contenant le nom de sa classe et la description textuelle de chacun de ses attributs. Par exemple,
la chaı̂ne de caractères produite pour un objet de classe Cercle est :
[Cercle
[centre de gravité : x=10 , y=4]
[rayon : 20]
[couleur : r=82 , g=255 , b=0]
]
– Toute forme possède un constructeur prenant en paramètre la couleur et initialisant les autres
attributs avec des valeurs par défaut de votre choix.
– La classe Cercle possède un constructeur prenant la couleur, le centre et le rayon comme paramètres.
– La classe Rectangle possède un constructeur prenant la couleur, le centre, la largeur et la hauteur
comme paramètres.
1. Dessiner la hiérarchie des classes de ce package.
2. En prenant exemple sur la réutilisation de la classe java.awt.Point pour réaliser la classe Cercle,
expliquez quelle est la différence entre l’agrégation et la composition. Par la suite nous utiliserons
la composition pour l’attribut centre.
3. Expliquez par un exemple le principe du polymorphisme (vous pourrez prendre exemple sur les
formes géométriques).
4. Ecrivez le code java des classes Forme et Rectangle. Apportez un soin particulier à l’encapsulation
et à la factorisation du code.
5. On souhaite ajouter la classe Carré comme spécialisation de la classe Rectangle. Cette nouvelle
classe doit répondre aux contraintes énoncées précédemment et ne doit pas pouvoir être dérivée.
Ecrivez le code de la classe Carré et prévoir en outre un constructeur prenant la couleur, le centre
et la longueur comme paramètres.
6. Ecrivez la classe Cercle.
On souhaite maintenant pouvoir composer des listes de formes géométriques pour opérer des regrou-
pements d’objets.
7. Créez une classe VectorFormes, un Vector qui ne peut contenir que des formes géométriques.
8. Ajoutez les méthodes translation et homothetie qui déplacent ou modifient la taille de l’ensemble
des objets de la liste. Ajoutez également la méthode toString() .

7
9. Redéfinissez la méthode add de la classe VectorFormes pour qu’un objet n’ayant pas de centre
défini soit placé au milieu des autres formes de la liste (en utilisant la moyenne ou la médiane par
exemple).
10. Ecrivez maintenant un main permettant de manipuler des formes et des listes de formes.
11. Quelles modifications doit-on apporter au code pour imposer que VectorFormes ne contienne que
des formes géométriques du même type, ce type étant paramétrable ?

Exercice 12. Banque


On considère une banque X ayant les caractéristiques suivantes :

– La banque permet à ses clients d’ouvrir des comptes qu’elle gère.


– Un compte est défini par un numéro d’agence, un numéro de compte et un solde.
– La banque gère deux types de compte : des comptes courants et des comptes d’épargne.
– Les clients doivent pouvoir déposer et retirer de l’argent sur un compte et consulter le solde d’un
compte.
– Un client doit pouvoir effectuer un virement d’un compte courant vers un autre de ses compte
(courant ou épargne).
– Un client doit pouvoir effectuer un virement vers le compte d’un autre client en payant le prix de
1,5 euros par transaction.
– Les comptes d’épargne rapportent de l’argent : ils ont un taux d’intérêt tx et chaque dépôt d’argent
d’un montant M sur un compte épargne provoque une augmentation du solde de ce compte de
M ∗ tx.
1. Proposez une modélisation en classes pour représenter la banque et son fonctionnement.
2. Codez et testez en créant un compte courant cc1, un compte d’épargne ce1 dont le taux est 0.05,
en effectuant un versement de 2000 euros sur cc1 puis un autre virement de 500 euros de cc1 vers
ce1.
3. Créez ensuite un vecteur paramétré de type Compte dans lequel on mettra les deux comptes cc1
et ce1. Affichez le contenu du vecteur à l’aide d’un appel de println sur le vecteur. Ajoutez enfin
un autre compte d’épargne ce2 du propriétaire de votre logement à qui vous verserez un loyer de
1000 euros.

III Interfaces, généricité


Exercice 13. Jeu de dames : réutilisation de code
Souhaitant réaliser un jeu de dames, nous envisageons de réutiliser la classe existante Plateau. Cette
classe permet de manipuler un tableau de pions pouvant servir au jeu de dames ou d’échecs. Sur ce plateau,
il n’y a pas de différence case noire / case blanche. C’est à l’application de placer puis de déplacer les
pièces de manière cohérente. La classe Plateau utilise l’interface Piece.
Ainsi, il est nécessaire de proposer une implémentation de l’interface Piece, et de spécialiser la classe
Plateau, afin de réaliser un jeu de dame. En plus nous envisageons de proposer 2 types de joueurs artificiels.
Vous trouverez les ressources nécessaires sur le site du module (un jar avec 2 fichiers).
1. Lire l’énoncé de l’exercice en entier (ce qui devrait toujours être fait) et représenter graphiquement
les liens unissant les différentes classes et interfaces.

8
Figure 1 – Damier et echiquier

2. Écrire la classe PieceDame qui décrit une pièce pour le jeu de dame. Une pièce a une couleur (noir
ou blanc) et un type (pion ou dame). Dans un souci de lisibilité, on pourra utiliser des constantes de
classe avec un nom explicite plutôt que des constantes entières non explicites (regarder les constantes
NOIR et BLANC de l’interface Piece par exemple).
3. Spécialiser le type Plateau en PlateauDeDame. En particulier le constructeur initialisera le plateau
en configuration de début de partie (comme sur la figure 1). Profiter du main pour tester la création
et l’affichage du damier.
4. Créer la classe JoueurDame (dont un attribut sera le plateau de dames) qui possède la déclaration
des méthodes suivantes :
– une méthode qui retourne vrai s’il abandonne, faux sinon
– une méthode pour jouer un coup.
Les méthodes ne doivent pas être implémentées. Ce sont des sous-classes de JoueurDame qui seront
instanciées. Comment doit donc être déclarée la classe ?
5. On souhaite créer une classe JoueurDameMauvaisJoueur qui abandonne dès qu’il a 2 dames de
moins que son adversaire et/ou 4 pièces de moins. Modifier la classe PlateauDeDame pour connaı̂tre
le nombre de dames et de pions d’un joueur. Implémenter dans JoueurDameMauvaisJoueur la
méthode d’abandon mais pas celle pour jouer un coup.
6. Ajouter une méthode à PlateauDeDame qui déplace une pièce d’une case à l’autre (les cases de
départ et d’arrivée seront données en argument) si et seulement si la case d’arrivée ne contient rien
et si le déplacement respecte la règle des dames (les cases doivent se situer sur la même diagonale).
La case de départ sera mise à null. La case d’arrivée pointera vers la pièce déplacée. La méthode
renverra un boolean indiquant si le déplacement a été effectué.
7. Créer une classe JoueurDameAgressif qui fait l’hypothèse qu’il est “en bas” du damier. Il n’aban-
donne que lorsqu’il n’a plus de pièce. Pour jouer il cherche parmi ses pièces si il peut prendre (càd
il y a un pion adverse devant et une case vide derrière). S’il n’y a aucune pièce à prendre, le joueur

9
Figure 2 – Exemple de prise de pièce

avance le pion le plus en recul (celui le plus en bas possible) ... Toujours en diagonale et de 1 case.
On ne tient pas compte des dames.
8. Spécialiser la classe JoueurDameMauvaisJoueur pour créer un mauvais joueur qui fait l’hypothèse
qu’il est “en haut” du damier et qui fait n’importe quoi (à vous de choisir l’implémentation de la
méthode pour jouer de la classe abstraite JoueurDame).
9. Créer une partie et faire s’affronter un mauvais joueur et un joueur agressif.

Exercice 14. Liste triée de doublets d’entiers


On souhaite manipuler un ensemble triée de doublets d’entiers. Pour comparer 2 doublets entre eux,
on compare le produit de leurs composantes.
1. Quelle est la différence entre un ensemble Set et une liste List ?
2. Quel conteneur choisir ? Quelle implication cela a-t-il sur les objets stockés ?
3. Écrire la classe DoubletComparable représentant un doublet d’entiers et qui soit manipulable par
une liste triée.
4. Écrire une application testant que l’insertion des éléments se fait dans le bon ordre et qu’un élément
est unique.
5. On souhaite, sans modifier la classe DoubletComparable, utiliser une nouvelle relation d’ordre pour
trier les doublets : les doublets sont comparés selon la somme de leurs composantes. Proposer et
tester une solution.

Exercice 15. Interfaces, extension de codes, “héritage multiple”


Cet exercice est la suite de l’exercice 3 du TD2. D’ailleurs, avez-vous commencé ou terminé l’exercice
3 du TD2 ? (Si non, c’est le moment de faire les deux.)
1. Dans un éditeur de diapositives (comme Microsoft Powerpoint ou Apple Keynote), les formes gra-
phiques appartenant au fond de la diapositive sont inchangeables tandis que les autres formes sont
déplaçables. Écrire l’interface Deplacable que les formes déplaçables implémenteront. Cette inter-
face contient les méthodes translationRelative(int dx, int dy) et translationAbsolue(int x, int y). Les
précédentes méthodes pour la translation ou l’homotétie ne servent plus ici.
2. Quel problème rencontre-t-on lorsque nous voulons créer des classes de Cercle, Rectangle et Carré
déplaçables tout en gardant (ou récupérant) les classes de Cercle, Rectangle et Carré non déplaçables
du TD précédent ? Y a-t-il une solution alternative ?

10
Exercice 16.
On souhaite représenter des nupplet. Par exemple un Doublet d’entiers est un nupplet pour n=2, et
constitue un couple d’entiers. Pour qu’un nupplet soit manipulable, il est souhaitable de pouvoir calculer
la somme et le produit de ses n composantes, et de prévoir une méthode add qui prend en argument un
autre nupplet et retourne un nouveau nupplet qui est la somme des deux.
1. Quel mécanisme objet proposez-vous pour modéliser un nupplet d’entiers, sachant qu’on spécialisera
par la suite ce nupplet en fonction de la valeur de n souhaitée ? Justifiez votre choix et proposez
une réalisation en Java.
2. Implémenter une classe Doublet d’entiers (pensez à écrire une méthode main pour tester chaque
méthode de votre classe et n’oubliez pas la méthode toString).
3. Implémenter une classe Triplet d’entiers en réutilisant au maximum ce que vous avez déjà fait.

Exercice 17. Généricité : la pile - 1ère partie


L’objectif est de construire une pile LIFO (last in first out). Pour cela utilisons les types paramétrés.
Le choix imposé est d’utiliser un tableau pour stocker les données de la pile. La taille de ce tableau doit
être passée au constructeur. Il existe également une constante de classe pour indiquer la taille par défaut.
Pour ce repérer dans la pile, la classe utilise ”un pointeur” qui indique l’indice de la case vide.
– Au départ, la pile est vide et le pointeur indique la case 0.
– Lors de l’ajout d’un élément (push), le pointeur est incrémenté, et lors du dépilement (pop) le
compteur est décrémenté.
1. Commencer l’écriture de la classe Pile, avec les attributs et les constructeurs.
2. Ajouter les méthodes push et pop . Dans cette première partie, nous traiterons les erreurs possibles
(la pile est vide ou pleine) par un affichage sur la sortie d’erreur.
3. Écrire un main permettant de faire un test et d’envisager les cas problématiques.
4. Désormais, nous souhaitons créer un nouveau type de pile PileOrdonnee : lors de l’ajout d’un
élément (appel de la méthode push), l’élément est inséré tel que les objets de la pile soit dans un
ordre croissant. Pour cela il est indispensable que les objets à empiler soit d’un type permettant la
comparaison. Quelle restriction proposez-vous pour le type des objets de cette nouvelle pile.
5. Pour créer la classe PileOrdonnee, nous souhaitons réutiliser la classe Pile. Comment fait-on ? Écrire
pour cela la ligne de définition de la classe.
6. Écrire la classe PileOrdonnee et tester la soigneusement.

IV Exception, Architecture pour un jeu et entrées/sorties


Exercice 18. Partie Humain contre Machine, Interface console
L’objectif de cet exercice est de mettre en place les bases d’un jeu de morpion. Ce jeu connu de tous
est simple, mais il possède néanmoins quelques propriétés intéressantes : il s’agit d’un jeu pour 2 joueurs
qui se joue au “tour par tour”, et dont l’espace de jeu est une grille où les joueurs disposent chacun
leur tour un pion. Pour ce TD, les interactions avec l’utilisateur se feront via la console. Dans les TDs
suivants, nous proposerons un mode d’interaction graphique puis l’écriture d’un mode de jeu en réseau.
Récupérez tout d’abord l’archive td4.jar sur le site du cours. Ce jar contient le package morpion.moteur
que vous allez inclure dans votre projet. Pour concevoir une architecture logicielle évolutive, il est fonda-
mental de définir des classes d’objets autonomes, indépendants et responsables. Une manière de démarrer
l’écriture d’un tel jeu est de :

11
1. commencer par en écrire la méthode main et toujours se placer du point du vue du moteur de jeu
(ou plus généralement de l’application) ;
2. respecter le fait que le moteur ne doit ni intervenir sur l’interface utilisateur ni induire de contraintes
la concernant ;
3. ainsi, le main peut s’écrire indépendament de l’implémentation des joueurs en interagissant uni-
quement avec une interface Joueur.
Regardez ensuite l’exemple de méthode main dans la classe PartieMorpionV0 : cette méthode assure
simplement le déroulement du jeu. Le fait que le joueur soit humain ou non n’a pas d’influence pas sur
son déroulement. De plus, le mode d’interaction utilisé (par exemple, console, interface graphique, réseau,
SMS, ...) par le joueur humain est transparent pour le moteur. Cette méthode main suppose donc que
la classe implémentant une interface Joueur est autonome et qu’elle gère elle-même l’interaction avec
l’utilisateur ou l’intelligence artificielle.
Voici la description des différentes classes comprises dans l’archive :
– La classe Jeu regroupe toutes les informations nécessaires au bon déroulement de la partie, notam-
ment : les deux joueurs, l’état du plateau et le joueur en train de jouer.
– L’interface Joueur représente un joueur. Du point de vue du moteur de jeu, un Joueur interagit
avec l’utilisateur et transmet les informations du Jeu au Joueur et vice versa. Un Joueur est défini
par un nom, sa marque (typiquement ’X’ ou ’O’) et une méthode joue(Jeu jeu).
– La classe abstraite JoueurGeneric propose un début d’implémentation de l’interface Joueur.
– La classe JoueurMachine représente un Joueur doté d’une intelligence artificielle. Cette classe
propose une implémentation de la méthode joue.
– La classe PartieMorpionv0 permet de gérer une partie entre 2 instances de JoueurMachine.
Par la suite, vous allez développer le package morpion.console qui contiendra les classes nécessaires à
un jeu en console.
1. On veut permettre à un utilisateur de jouer contre un joueur artificiel. Pour cela, proposez une
implémentation de JoueurHumain, dans laquelle l’interaction avec l’utilisateur se fait via la console.
Ceci nécessite de demander à l’utilisateur d’entrer un couple de coordonnées correspondant à l’en-
droit où il souhaite placer son pion. Par exemple, l’humain (c’est-à-dire vous) entre ”0 2” pour
placer un pion sur la ligne 0 et la colonne 2 (il faudra bien sûr vérifier les valeurs saisies). Voici un
exemple de l’échange :
C’est au tour de IA :(Halbert,O)

---
|?|?|?|
|?|?|?|
|?|?|?|
---

<<< 1 1
C’est au tour de Humain :(Robert,X)

---
|?|?|?|
|?|O|?|
|?|?|?|
---

>>> 0 3

12
>>> 0 2

2. PartieMorpionv0 joue une partie entre 2 joueurs artificiels. Pour tester votre joueur humain, créez
une nouvelle classe PartieMorpionv1.

Exercice 19. Interaction et export texte


1. Au début d’une partie entre un humain et la machine, le programme demande à l’utilisateur la
marque qu’il veut utiliser pour cette partie. Dès que l’utilisateur a entré une marque correcte, la
partie commence. Voici un exemple de dialogue :
Quelle marque voulez-vous : X ou O ?
O
Écrivez la classe PartieMorpionv2 qui met en place cette interaction.
2. À chaque tour de jeu, on souhaite proposer à l’utilisateur le choix suivant :
(save and quit) or quit or continue (s/q/c) ?
Si l’utilisateur choisit ”save and quit”, le contenu du plateau doit être écrit dans le fichier de son
choix. Mettre en place cette interaction et la sauvegarde du jeu.

Exercice 20. Des exception pour la pile


Reprenons l’exercice du TD3 sur la pile. Normalement, cet exercice est terminé, sinon il est justement
temps de le terminer. Nous allons maintenant considérer deux cas d’utilisation ”imprévus” :
– La pile est pleine alors que l’on tente d’ajouter un élément (push) dans la pile.
– La pile est vide alors que l’on tente de retirer un élément (pop) de la pile.
Pour traiter ces cas, nous allons utiliser le mécanisme des exceptions.
1. Écrire deux classes PilePleineException et PileVideException pour ces deux cas.
2. Modifier la méthode push afin qu’elle jette une Exception si la pile est pleine. Dans le main faites
les tests nécessaires.
3. Faites de même pour la méthode pop.
4. Si par exemple la classe PileVideException hérite de RuntimeException et non plus d’Exception,
quelle est la différence ?
L’objectif de ce TD est de mettre en place les bases d’un jeu de morpion. Ce jeu connu de tous est simple,
mais il possède néanmoins quelques propriétés intéressantes : il s’agit d’un jeu pour 2 joueurs qui se joue
au “tour par tour”, et dont l’espace de jeu est une grille où les joueurs disposent chacun leur tour un
pion. Pour ce TD, les interactions avec l’utilisateur se feront via la console. Dans les TDs suivants, nous
proposerons un mode d’interaction graphique puis l’écriture d’un mode de jeu en réseau.

Exercice 21. Serialisation


On veut maintenant pouvoir sauvegarder une partie en cours afin de pouvoir la reprendre plus tard.
Pour cela, on posera une question à l’utilisateur avant chaque nouveau coup, comme :
Appuyez sur la touche Entrée pour continuer ou tapez la commande ‘‘quit’’ pour quitter.
> quitter

13
Ce dialogue est implémenté dans la méthode continuerOuQuitter de la classe Jeu, qui peut donc faire
appel à la méthode public static void sauveJeu(Jeu j).
1. Compléter les méthodes public static void sauveJeu(Jeu j) et public static void chargeJeu(Jeu j) de
la classe IOMorpion afin de pouvoir respectivement sauvegarder un jeu et charger un jeu.
2. Afin de tester votre programme, modifiez le main de la classe PartieMorpionv1 afin qu’il soit possible
de sauvegarder et quitter une partie en cours. Jouez un peu en utilisant la classe PartieMorpionv1
(mais pas trop longtemps tout de même !). Essayez notamment la commande quit qui entraı̂ne la
sauvegarde du jeu. Puis écrivez la classe PartieCommencee qui redémarre une partie à partir d’une
sauvegarde.
3. On souhaite maintenant introduire un peu d’aléatoire lorsqu’une partie sauvegardée est reprise : si
le plateau contient autant de jetons des deux joueurs (autant de ’X’ que de ’O’), alors le joueur qui
commence est tiré au hasard. Sinon, c’est bien évidemment le joueur qui a le moins de jetons placés
qui (re)commence. Modifiez le processus de sérialisation pour remplir ces spécifications.

Exercice 22. Options de jeu


Au début d’une partie entre un humain et la machine, le programme demande à l’utilisateur la marque
qu’il veut utiliser pour cette partie. Dès que l’utilisateur a entré une marque correcte, la partie commence.
Voici un exemple de dialogue :
Quelle marque voulez-vous : X ou O ?
O
Écrivez la classe PartieMorpionv2 qui met en place cette interaction.

V Interfaces Graphiques
L’objectif de ce TD est de vous familiariser avec les principaux composants graphiques des librairies
SWING, afin de pouvoir concevoir des interfaces graphiques en JAVA. Etant donné la richesse des librai-
ries, il est impossible de tout connaı̂tre par coeur. Ce travail est donc aussi l’occasion d’apprendre à se
familiariser avec la documentation de java. Ce TD pourra s’étendre sur celui de la semaine prochaine.

Exercice 23. La documentation de java et Swing


La documentation de java est disponible en ligne sur le site de Sun Microsystems à l’url :

http://java.sun.com/javase/6/docs/api/
La page d’accueil de la documentation donne un certain nombre de pointeurs vers des documents
utiles mais ce qui nous intéresse le plus ici est le lien appellé Java Platform API Specification, qui
donne accès à la documentation de l’ensemble des classes existantes du jdk. Cette documentation a été
générée automatiquement à l’aide de javadoc. Cet outil est capable d’exploiter un format spécial de
commentaires dans des sources java, pour générer automatiquement une documentation au format html,
consultable à l’aide de n’importe quel navigateur. De plus, pensez à utiliser les tutoriaux de sun qui vous
guident sur les choix à faire parmis les composants graphiques et et donnent des exemples d’utilisation.
1. Familiarisez vous avec l’organisation de la doc.
2. Retrouver les pages décrivant les classes JFrame, JPanel et JButton.

14
3. Pour chacune de ces classes, trouver à quel package elles appartiennent, quels sont leurs construc-
teurs, de quelles classes elles dérivent (i.e. leur position dans la hierarchie des classes) et les méthodes
qui leurs sont applicables (notamment par héritage). Après une lecture attentive de la documenta-
tion des 3 classes (JFrame, JPanel et JButton), vous devez avoir compris le fonctionnement global
de ces classes. Sinon posez des questions.

Exercice 24. Conception d’une interface graphique simple


Réalisons une interface pour une calculette simple, que nous augmenterons au fur et à mesure. La
calculette elle-même n’est pas à réaliser. Nous vous fournissons une classe Calculette déjà toute prête.
De façon générale, lors de la mise en œuvre d’une application informatique, il est indispensable de ne pas
mélanger les fonctionalités principales de l’application, avec les aspects liés à l’interface utilisateur. Il vaut
mieux concevoir l’interface séparément et l’associer à l’application centrale. Ceci nécessite naturellement
de bien comprendre et spécifier au préalable, comment les deux composantes communiquent.
Dans le cas présent, vous ne disposez pas du source de la classe Calculette mais seulement du fichier
Calculette.class. Vous pouvez néanmoins vous faire une idée de ce que cette classe met à disposition
grâce à Eclipse ou Netbeans ou à l’aide de la commande :
javap <NomDeClasse>
Cette commande vous indiquera la liste des constantes et méthodes publiques définies dans cette classe.
Utiliser cette commande pour inspecter la classe Calculette.
La calculette dont nous disposons ici est supposée disposer de 16 touches, correspondant aux 10
chiffres, aux quatre opérations de base, et aux touches [=] et [C/CE]. La méthode input(n) sert ici à
répercuter sur la calculette ce qui se passe lorsque l’on appuie sur l’une des touches. La méthode getAff()
permet de récupérer le contenu de l’écran de la calculette, qui peut être affiché à l’aide de la méthode
affiche().
La plupart des interfaces graphiques se définissent en dérivant une nouvelle classe à partir de la classe
JFrame.
1. Définir une classe IGCalculette, comme une extension de JFrame. Cette classe devra contenir un
attribut lui permettant d’interagir avec une calculette. Cependant cette calculette devra également
pouvoir transmettre les résultats de ses calculs à l’interface Graphique. Comme ici nous ne dis-
posons que du fichier class de la classe Calculette, nous devons passer par la définition d’une
classe intermédiaire CalculetteGraphique comme une extension de la classe Calculette. La classe
CalculetteGraphique pourra alors comporter un attribut de la classe IGCalculette.
2. Bien que l’on puisse ajouter directement des composants sur un JFrame, il est généralement
préférable de regrouper préalablement les différents composants de l’interface dans différents JPanel
que l’on intègre dans la fenêtre principale. Définir deux JPanels. Le premier servira pour le clavier de
la calculette. Le second servira pour l’écran et éventuellement des composants permettant de confi-
gurer l’affichage sur l’ecran. Afin de pouvoir visualiser ces deux JPanels, leur donner des couleurs
de fond différentes..
3. Pour le choix du composant graphique dédié à l’écran, que proposez-vous ? On pourra se reporter
au tutoriaux de Sun pour avoir un aperçu des possibilités. Intégrer le composant de votre choix
dans l’interface graphique.
4. Définir dans la classe IGCalcultette une methode publique d’accès permettant de modifier la
valeur du texte de cet attribut puis, dans la classe CalcultetteGraphique, surcharger la méthode
affiche() afin qu’elle modifie dans l’interface graphique associée la valeur de l’écran.
Tester que cela fonctionne bien en réalisant une opération simple dans une méthode main(...) de
la classe CalcultetteGraphique.

15
Exercice 25. Ajoutons le clavier
Pour réaliser le clavier de la calculette, nous allons utiliser des composants de type JButton. Ces
composants étant utilisés pour le contrôle de l’interface, il est nécessaire de les déclarer comme attributs
de la classe IGCalculette.
1. Creer un nouvel attribut pour l’interface graphique, correspondant à un tableau boutons pour le
clavier (nb : on pourra de façon utile créer des constantes de classe pour référencer les boutons qui
ne correspondent pas à des chiffres par des entiers).
2. Lors de l’initialisation de l’interface, créer les 16 boutons , chacun d’entre étant étiqueté par le
symbole approprié, et disposer le tout de façon adéquate sur le pannel correspondant au clavier.
Quel est le Layout manager qui vous semble le plus approprié ?

Exercice 26. Conception d’une l’interface réactive


Pour l’instant notre interface graphique est quasiment passive. Nous avons pu constater que la calcu-
lette graphique était capable d’agir sur l’écran de l’interface, mais l’interface n’est pas capable d’agir sur
la calculette. Pour cela, il est nécessaire d’associer à chacun des composants graphiques un gestionnaire
d’événements.
1. Les JButtons sont généralement associés à des gestionnaires qui implémentent l’interface ActionListener.
Ici chaque bouton du clavier a un effet différent. On peut imaginer créer un gestionnaire différent
pour chaque bouton... mais avec 16 boutons, cela risque d’être un peu lourd !. Une autre façon
de faire est de ne créer qu’un seul gestionnaire, que l’on associe à chaque bouton, mais dont la
méthode ActionPerformed qu’il implémente, est capable d’adapter son action en fonction du bou-
ton qui a été cliqué. Pour connaı̂tre le bouton à l’origine de l’événement, on peut utiliser la méthode
getSource de la classe EventObject.

Implémenter une classe interne pour un tel gestionnaire d’événements et associer ce gestionnaire à
l’ensemble des boutons du clavier.
Tester maintenant la réactivité de votre interface graphique aux événements concernant le clavier
de votre calculette graphique.
2. Vous avez sûrement remarqué que la fermeture de la fenêtre correspondant à votre interface gra-
phique de calculette ne provoque pas pour autant la terminaison du processus java. Que faut-t-il
faire ?

Exercice 27.
Nous souhaitons illustrer l’utilisation d’autres composants SWING en rajoutant quelques outils de
contrôle de l’affichage de l’écran.
1. Créer un nouveau JPanel pour recevoir ces outils de configuration et le disposer au dessus de la
zone d’affichage dans le pannel correspondant à l’écran.
2. Ajouter successivement sur ce panel
(a) un JLabel étiqueté par ”Fonte”
(b) un JComboBox auquel on associera des noms de fontes
(c) deux JCheckBox auquelles on associera les chaı̂nes ”gras” et ”italique”
Ces outils seront utilisés pour contrôler le choix de la fonte utilisée pour la zone d’affichage.

16
3. Comme lors de l’exercice précédent, associer un gestionnaire d’événements aux deux JCheckBoxes
permettant de contrôler le style de l’affichage de l’écran (ici, c’est l’interface ItemListener qui doit
être implémentée).
4. De manière similaire, associer un gestionnaire d’événements à la JComboBox permettant de contrôler
la police de l’affichage de l’affichage.

VI Dessin et Evénements : le morpion - suite


Le but de cet exercice est faire un jeu de morpion en version graphique. Ce TD poursuit le TD 4.
Comme ce que vous avez vu en cours : toutes les interactions avec l’utilisateur sont à la charge de la
classe abstraite de type Joueur. Résumons rapidement le code :
– Le moteur de jeu : la classe Jeu en est le centre, elle est manipulée par le main de notre application :
PartieMorpion et autre version. Mais encore ...
– C’est à la classe abstraite Joueur que l’ensemble des classes du moteur s’adresse (on pourrait même
faire un interface). Ainsi le moteur n’est en rien concerné par les interactions avec utilisateur : par le
fait qu’un joueur soit humain, local ou distant, avec une interface utilisateur console ou graphique.
Cette séparation entre le joueur et le reste de l’application peut paraı̂tre artificielle mais rendra
notre code plus modulable pour ce TD et les suivants (passage au réseau).
– Cette interface a donné lieu à une première implémentation JoueurHumain (au TD 4 pour l’inter-
action en mode console).
– Dans le cadre de ce TD, vous allez créer la classe JoueurHumainG qui proposera une interface
graphique à l’utilisateur.

Exercice 28. Représentation graphique de la grille


La représentation de la grille du morpion est simple et doit ressembler à ceci :

1. Créer une classe GrilleMorpionG qui hérite de JComponent. Cette classe contient un attribut privé
qui sera un tableau de caractères (comme dans la classe Jeu) qui contient localement les informations
liées à la grille.
2. Ecrire le constructeur de la classe permettant entre autre l’utilisation des images O.gif et X.gif
pour représenter les coups des joueurs. A cet effet, la classe contient deux champs Image (imageX
et imageO) qui sont deux éléments dessinables dans un contexte graphique et qui font référence à
deux images de 50*50 pixels, une croix et un rond.
3. Redéfinir la méthode paintComponent qui redessine la grille à partir du tableau de caractères. Pour
tester votre code, écrivez une méthode main créant une JFrame dans laquelle vous placerez votre
composant graphique.
4. Ecrire la méthode refresh prenant en paramètre un objet de la classe Jeu qui redessine la grille en

17
Exercice 29. L’interface graphique : le joueur
Créons l’interface graphique du jeu qui est gérée par la classe JoueurHumainG qui hérite de la classe
JoueurHumain.
1. Commencer l’écriture de la classe JoueurHumainG. Cette classe possède comme attributs spécifiques :
un objet de type JFrame, sa grille graphique, deux entiers qui représentent les coordonnées que le
joueur souhaite tenter, et la référence sur un objet de la classe Jeu.
2. Ecrire le constructeur de la classe prenant en paramètre un caractère qui sera la marque du joueur.
3. Ajouter à la grille graphique un MouseListener qui sera l’implémentation anonyme d’un Mou-
seAdapter. Ce Listener récupère les coordonnées du clic gauche de la souris et les convertit en
coordonnées utilisables par le jeu. Ces coordonnées seront stockées dans les attributs entiers prévus
à cet effet.
4. Comment faire pour que les clics de la souris ne soient pris en compte que lorsque c’est au tour du
joueur ?
5. Redéfinir la méthode afficheJeu.
6. Redéfinir la méthode joue : cette méthode peut s’inspirer de celle de JoueurHumain.
7. Tester votre application en créant une partie entre deux joueurs humains, l’un en mode graphique
et l’autre en mode console. Pour cela, inspirez-vous de la classe HumainVSMachine.

Exercice 30. Choisir sa marque


Au démarrage d’une partie, on souhaite que l’utilisateur puisse choisir sa marque à l’aide d’une boı̂te
de dialogue ressemblant à l’image ci-dessous :

La partie ne commence que lorsque l’utilisateur appuie sur le bouton ’ok’. Comme précédemment, on
souhaite que toute l’interface avec l’utilisateur soit gérée par le joueur et qu’elle reste transparente pour
le moteur. Pour cela, créez un nouveau type de joueur humain permettant à l’utilisateur d’effectuer le
choix de sa marque.

Exercice 31. Deux joueurs humains en local : deux fenêtres


On souhaite pouvoir jouer à deux humains. L’option la plus simple est de créer deux joueurs ayant
chacun sa fenêtre.
1. Modifier le main de votre application pour passer à deux joueurs humains.
2. Seul le premier joueur peut choisir sa marque. Comment faire ? De plus comme précédemment, les
clics de la souris dans une fenêtre donnée ne doivent avoir des conséquences que si c’est le tour du
joueur correspondant.
3. L’interface actuelle n’est pas très parlante, ajouter une étiquette (label) indiquant à chaque instant
quel joueur doit jouer.

18

Vous aimerez peut-être aussi