Bonjour à tous, j'ai essayé de convertir un code de ma prof pour pouvoir passer à une interface totalement graphique et donc utiliser seulement des boutons...
Mais avec mon nouveau code, je n'ai réussi à obtenir qu'une multitude d'erreurs...
Je précise que j'ai regardé tout ce qui est boutons/interface graphique ce soir et que c'est toujours absolument flou donc vous allez surement etre catastrophés devant mon code.
En premieres erreurs j'ai:
Jeu.java:35: error: cannot find symbol
this.add(boutons, BorderLayout.SOUTH);// TEST, jb remplacé par boutons
^
symbol: variable boutons
location: class Jeu
Jeu.java:76: error: local variable adversaire is accessed from within inner class; needs to be declared final
if(c.contenuDansFenetre(w,h) && !adversaire.intersectionCercle(c) && !j.intersectionCercle(c)) {
^
Et voilà le code que j'ai modifié:
/**
* La classe Jeu définie le Jeu, son interface graphique et les interactions
* avec les joueurs dans un terminal lors d'une partie.
* @author Diane Lingrand
**/
import java.io.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Jeu extends JFrame {
// variables d'instance
private final int NB_DE_COUPS_MAX = 20; // doit être pair
private int w;
private int h;
private Joueur j1;
private Joueur j2;
private boolean premierJoueur=true;
private boolean pasFini = true;
private PanelJeu pj;
/** Constructeur: initialise une partie: taille de surface et noms des joueurs
** @param _w : largeur de la surface de jeu
** @param _h : hauteur de la surface de jeu
** @param n1 : nom du premier joueur
** @param n2 : nom du second joueur
**/
public Jeu (int _w, int _h, String n1, String n2) {
super("Jeu des cercles");
j1 = new Joueur(n1);
j2 = new Joueur(n2);
pj = new PanelJeu(_w,_h,j1,j2);
this.setLayout(new BorderLayout());
this.add(pj, BorderLayout.CENTER);
this.add(boutons, BorderLayout.SOUTH);// TEST, jb remplacé par boutons
w = _w;
h = _h;
}
/**
* un coup dans le jeu:
* demande au joueur ce qu'il veut faire
* lit sa réponse
* effectue l'action demandée
* si l'action conduit à une intersection avec les bords de l'espace
* ou bien avec un autre cercle, le boolean pasFini est mis à false
**/
public void action(boolean joueur) throws IOException {
Joueur j, adversaire;
if(joueur) {
j = j1;
adversaire = j2;
}
else {
j = j2;
adversaire = j1;
}
JPanel boutons=new JPanel();
JButton jb = new JButton("Quitter");
JButton ajout = new JButton("Ajouter cercle");
JButton deplac = new JButton("Deplacer cercle");
JButton chang = new JButton("Changer rayon");
JButton aff = new JButton("Afficher");
boutons.add(ajout);
boutons.add(deplac);
boutons.add(chang);
boutons.add(aff);
boutons.add(jb);
jb.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {System.exit(0);}});
ajout.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {
System.out.println("Quel centre ?");
int x = StdInput.readlnInt();
int y = StdInput.readlnInt();
Cercle c = new Cercle(x,y);
if(c.contenuDansFenetre(w,h) && !adversaire.intersectionCercle(c) && !j.intersectionCercle(c)) {
if(!j.ajouteUnCercle(c))
pasFini = false;
pj.repaint();
}
else
pasFini = false;}});
deplac.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {
System.out.println("Quel numero de cercle ?");
int numero = StdInput.readlnInt();
System.out.println("Quelle translation ?");
int tx = StdInput.readlnInt();
int ty = StdInput.readlnInt();
if(!j.translateUnCercle(tx,ty,numero))
pasFini = false;
if(j.intersectionJoueur(adversaire) || !j.contenusDansFenetre(w,h))
pasFini = false;
pj.repaint();}});
chang.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {
System.out.println("Quel numero de cercle ?");
int num = StdInput.readlnInt();
System.out.println("Quel nouveau rayon ?");
int rayon = StdInput.readlnInt();
if(!j.changeLeRayonDunCercle(rayon,num))
pasFini = false;
if(j.intersectionJoueur(adversaire) || !j.contenusDansFenetre(w,h))
pasFini = false;
pj.repaint();}});
aff.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {
System.out.println(j);
System.out.println(adversaire);}});
this.add(boutons, BorderLayout.SOUTH);// TEST, jb remplacé par boutons
}
/** boucle de jeu
* on sort de la boucle soit par erreur d'un joueur
* soit au bout de NB_DE_COUPS_MAX coups
* détermine le gagnant et affiche un message de félicitation
**/
public void joue() throws IOException {
int compteur = 0;
while(pasFini && compteur++ < NB_DE_COUPS_MAX) {
if(premierJoueur)
System.out.println("Joueur 1: quelle action ?");
else
System.out.println("Joueur 2: quelle action ?");
action(premierJoueur);
premierJoueur = !premierJoueur;
}
if(compteur == NB_DE_COUPS_MAX+1) {
double s1 = j1.surface();
double s2 = j2.surface();
if(s1 > s2)
System.out.println("Bravo au joueur " + j1.getNom() + " : " + s1 + " > " + s2);
else if(s1 > s2)
System.out.println("Bravo au joueur " + j2.getNom() + " : " + s2 + " > " + s1);
else
System.out.println("Bravo aux joueurs " + j1.getNom() + " et " + j2.getNom());
}
else {
if(premierJoueur)
System.out.println("Joueur " + j2.getNom() + " a perdu");
else
System.out.println("Joueur " + j1.getNom() + " a perdu");
}
return;
}
/** création et lancement d'une partie **/
public static void main(String [] args) throws IOException {
int w = Integer.parseInt(args[0]);
int h = Integer.parseInt(args[1]);
Jeu j = new Jeu(w,h,args[2],args[3]);
j.pack();
j.setVisible(true);
j.joue();
}
}
Je ne comprends pas déjà l'erreur local vvar is accessed from within inner class, et je ne sais pas comment utiliser correctement les boutons...
Merci à ceux qui prendront le temps de lire, répondre, et peut etre me corriger
Lien pastebin : http://pastebin.com/sc0MxAVv
Plus lisible. A l'avenir, n'oublie pas d'anonymiser ;)
Sinon... tu as beaucoup de problèmes dans ce code !
Pas à pas : NB_DE_COUPS_MAX devrait être static, c'est une constante à priori, et les constantes sont en final static en Java.
Les noms de tes variables sont affreux ! Les variables à une lettre, c'est uniquement bon dans les opérations mathématiques ou pour les boucles.
Prend l'habitude de bien nommer tes variables, tes méthodes et tes classes, et d'utiliser l'anglais pour ça.
De plus, il est plutôt commun que les variables booléennes soient nommées sous forme interrogatives.
"premierJoueur" => "isFirstPlayer"
Les "_" en Java ne sont tolérées qu'à un seul endroit : dans le nom des constantes.
N'hésites pas a aérer le code. Tu n'es pas payé à minifier le code ou à faire des one-liners.
Il manque toutes les annotations @Override sur tes méthodes surchargées. De base, cette annotation ne sert pas à grand chose, mais elle aide à la visibilité. En un coup d'oeil, l'on sait si la méthode est héritée et surchargée ou non.
Une méthode qui retourne un "void" n'a pas besoin de return, sauf cas particulier style gestion d'erreur (encore que...).
Pour que ton programme compile, il suffit juste de virer ta ligne "this.add(boutons, BorderLayout.SOUTH);// TEST, jb remplacé par boutons" du constructeur. L'erreur te le dit clairement d'ailleurs : tu utilises une variable "boutons", mais tu n'as aucune variable correspondante dans cette portée.
Tu as, par contre, toujours pas mal de problème pour que ton jeu soit "purement graphique".
Je ne sais pas comment fonctionne StdInput (classe non-standard) mais j'imagine que c'est la lecture du clavier pour la console.
Tu ne devras absolument plus l'utiliser pour passer aux interfaces graphique.
Pour le reste... tu as de gros souci conceptuels et ta transition ne fonctionnera pas du tout (pour le moment, tu es mi-graphique, mi-console, avec quelques souci d'algo).
Par contre, cela viendra petit à petit avec ta compréhension des interfaces graphique, qui est une part difficile mais absolument importante.
Indice : raisonne en action/réaction, pas en attente d'événements ou d'entrées utilisateur.
Code compilable renommé (apprend aussi à utiliser la fonctionnalité "refactor" de ton outil de développement) : http://pastebin.com/V02ZaK7X
Salut et merci pour cette réponse complète!
Je tiens cependant à repréciser que ce code n'est pas de moi mais bien de ma prof!
En fait, le seul truc que j'ai modifié, en mal évidemment, c'est à tous les endroits ou j'ai mis des listeners (donc les aff.addActionListener etc) il y avait avant un gros switch case qui agissait selon ce que voulait faire l'utilisateur.
Le truc c'est que j'ai tenté de modifier ça naivement, mais je n'ai pas vu les interfaces graphiques en cours et j'ai quand meme voulu tenter de me lancer dedans...
En ce qui concerne le nom des variables, je n'aurais pas choisi ces noms là de toute façon, j'ai juste laissé ceux de la prof.
Je vais donc bien regarder le code compilable que tu as mis pour essayer de tout comprendre, bien que je ne sois qu'au début de la programation java (officiellement d'ailleurs je viens à peine de débuter la leçon sur les classes... et du coup Override ça me dit rien)
Le problème qu'il me reste actuellement est que je ne sais pas comment remplacer les 7 stdInput que j'ai dans le code...
Parce que je souhaite récuperer des valeurs rentrées par l'utilisateur et je n'ai aucune idée de comment le faire autrement, et si je les laisse j'ai une unreported exception IOException...
(Désolé du triple post)
J'aurais eu une idée de "comment je voudrais rendre la chose" mais je n'ai malheureusement pas les compétences nécéssaires pour y parvenir;
Je m'imaginais faire en sorte que lorsqu'on appuie sur "ajouter un cercle", cela ouvre une nouvelle fenetre avec deux champs, ou on pourrait mettre la coordonnée x et la coordonnée y pour le centre du cercle. Cela remplacerait donc les deux stdInput et le reste du listener "ajouter cercle" s'executerait seulement apres validation des champs pour choisir x et y.
Et ensuite je comptais simplement faire pareil avec les autres fonctions:
- cliquer sur deplacer cercle ouvrirait une fenetre avec les champs "numéro","x","y"
- cliquer sur changer rayon ouvrirait une fenetre avec les champs "numéro", "rayon"
Mais je ne vois pas comment procéder
En passant, est ce que quelqu'un peut m'expliquer a quoi sert @override ? Perso, j en'ai jamais vraiment compris. a priori c'est juste a destinatoin de la javadoc, mais les gens sont a cheval dessus donc j'ai du loupe quelquechose.
^^
De mon coté, je m'en sers uniquement comme indicateur visuel direct pour savoir si une méthode en surcharge une autre ou non. Ca évite d'avoir des surprises.
Cette annotation est aussi utilisée par le compilateur pour vérifier qu'elle surcharge bien une méthode.
Ca permet ainsi de s'éviter des problèmes lors de refactor qui se déroulent mal, mauvaise écriture de méthode...
Elle peut aussi être utilisée de manière custom, je pense, pour faire un listing de toute méthode surchargée dans un projet, faire un graphe de lien entre classe (une classe en étend une autre et redéfinit 70% des méthodes, une autre 0%).
Je pars en conjecture, par contre.
@Yozamu : Je m'en doutais pour le code venant d'un prof. J'ai quand même préféré tout reprendre, autant indiquer les bonnes pratiques
Le coup de la popup qui demande les champs, tu commences à comprendre comment faire. C'est un moyen de le faire.
Pour faire ceci, je t'indique la classe JDialog.
Ressources : http://docs.oracle.com/javase/6/docs/api/javax/swing/JDialog.html
http://docs.oracle.com/javase/tutorial/uiswing/components/dialog.html
Dans ton cas, l'appui ouvre une JDialog contenant 5 composants graphique :
- 2 JLabel : pour indiquer à quoi servent les champs
- 2 JTextField : pour les entrées utilisateur
- 1 JButton normalement généré par la JDialog, mais sur lequel il faudra mettre un listener
Ca fait très longtemps que je n'ai plus fait de Swing.
En me relisant, je n'ai pas donné d'explications sur le "final" et les classes internes.
Donc, quand tu as une classe anonyme utilisant une variable de la classe englobante, il y a un problème de portée de variable.
Dans ton cas, c'est, par exemple, l'utilisation de la variable "adversaire" dans un listener. Ce listener est une classe anonyme interne. Celui-ci connait de la classe englobante :
- ses variables membres
- ses méthodes publiques
- ses méthodes privées
- ses méthodes protected
Il ne connait pas les variables locales.
Dans ton cas, tu tentes de passer une variable locale au listener. Celui-ci ne les connait pas, elles ne sont pas dans sa portée, un listener étant par défaut attaché à quelque chose, il ignore tout le code l'entourant, il en est désolidarisé.
Dans le fichier, il est à la suite. Dans la réalité, après la compilation, le listener se trouvera dans un autre fichier représentant sa classe anonyme et qui aura comme nom LaClasseEnglobante$1.class
Ainsi, en appliquant le modificateur "final" devant ces variables locales, on change sa portée. On lui garanti "cette variable-là, tu peux l'utiliser et faire un lien, elle n'aura qu'une seule valeur". Le truc un peu compliqué a comprendre, c'est qu'elle n'aura qu'une seule valeur (à cause du final), mais puisque c'est une variable locale qui changera lors de l'appel de la méthode englobante, sa valeur changera.
Oui... mais non en fait. Durant la séquence d'utilisation du listener, il est garanti que la valeur de la variable ne change pas (et j'insiste, la variable peut se voir modifier en interne via ses méthodes, mais la variable pointera toujours sur le même objet durant la séquence).
Ce matin j'ai tenté des trucs et j'ai réussi à faire ce que je disais hier!
Le problème reste maintenant à organiser le tour à tour...
Je copie déjà mon fichier actuel:
http://pastebin.com/T1a9HFwE
Donc le problème est maintenant dans la méthode joue() puisqu'elle continue comme avant, c'est à dire qu'il y a un while, une incrémentation du compteur à chaque tour, mais maintenant un tour ne se déroule plus comme avant puisque il n'y a plus que des appuis sur boutons...
(d'ailleurs, je n'ai fini que le premier, "ajouter cercle", je ferai les autres apres avoir résolu mon probleme)
Du coup j'aimerais changer ce systeme pour incrémenter le compteur et changer le joueur qui joue après qu'une action ait été effectuée, donc par exemple quand le joueur a cliqué sur ajouter cercle, puis dans la seconde boite cliqué sur "ok"(c'est ce que j'ai essayé de faire avec le compteur++)
Et il faudrait donc aussi que une fois le compteur arrivé au max on termine la partie...
Finalement, il s'agit de transcrire le code actuel qui gérait le tour à tour sur la console, pour maintenant le gérer en fonction des appuis sur les boutons qui permettent les actions.
Bien sur, je n'ai aucune idée de comment faire puisque je ne connais pas assez le fonctionnement des interfaces graphiques et des boutons.
J'aimerais donc bien une petite aide, pour que je puisse analyser comment le code serait construit à partir du mien
La réponse à ta question est la réponse à cette question : quand est-ce qu'un joueur finit son tour ?
Est-ce en appuyant sur un bouton ? Donc le compteur s'incrémente à ce moment et change de joueur.
Est-ce après une action spécifique ? Donc c'est ici qu'on change.
A priori, dans ton cas, c'est après une action. Donc, change de joueur et incrémente le compteur après une action ![]()
C'est justement ce que j'ai essayé de faire dans la fin de mon code là ou j'ai mis compteur++. Il faudrait également que je change ici le joueur qui va jouer, et j'ai bien compris comment on devait faire, mais je ne comprends absolument rien a la portée des variables avec les boutons que j'utilise ici et donc je suis incapable de changer les variables correctement!
D'ailleurs au passage, j'ai une question:
quelle erreur est présente dans mon code ?
Parce que une fois compilé, quand je lance le programme, je suis obligé de le quitter plusieurs fois car la moitié du temps(voire plus) les boutons ne s'affichent pas quand je lance le jeu. Je ferme donc le jeu et le rouvre(sans recompiler ni rien) et des fois les boutons s'affichent, des fois non, donc je ne comprends pas.
Tu n'as plus besoin de la méthode "joue", en fait.
Une interface graphique n'est qu'action/réaction. Tu dois afficher l'interface, et mettre en place les bons listeners sur tes boutons pour que ça fonctionne.
Attention, convention Java : le nom des classes en UpperCamelCase, le nom du reste en lowerCamelCase, sauf pour le NOM_DES_CONSTANTES.
Pour la portée des variables...
Une variable locale n'est connue que dans le bloc où elle est déclarée (ce qui inclue les blocs existant dans ce bloc). Elle est connue à partir de la ligne de sa déclaration.
Une variable finale a le même scope qu'une variable locale, sauf qu'elle peut être fournie à certaines classes anonymes.
Une variable d'instance est connue par toute la classe, mais seulement par une instance de celle-ci. Avoir par exemple une :
public class Example {
....public int test = 0;
}
Une instance de la classe "Example" pourra avoir sa variable test à 5 tandis qu'une autre l'aura à 8 (ceci est un exemple). Le modificateur de visibilité "public" a été mis pour l'exemple.
Une variable de classe est connue et partagée par toute les instances de la classe. Un changement sur une est répercutée pour toute les autres. C'est une simili-variable globale, ce qui fait qu'on préfère qu'elle soit constante pour éviter les effets de bords.
Attention, ne pas abuser des globales, ça devient vite bordélique.
Par "fermer et rouvrir", tu entends vraiment fermer et rouvrir ? Ou plutôt minimiser et remettre ?
Je penche pour la seconde solution. Dans ce cas, ce serait un problème de repaint().
Chez moi, ta fenêtre s'affiche bien à chaque fois, mais dans le coin supérieur gauche, et très petite.
Ca fonctionne mieux en rajoutant un
"jeu.setMinimumSize(new Dimension(width, height));" juste avant le ".pack();".
A mon sens, tu n'as pas besoin de demander la largeur et la hauteur en arguments, juste de fixer une taille minimum dans le code.
La méthode "JFrame#setMinimumSize(Dimension)" demande à ce que la fenêtre ne puisse jamais être plus petite que la Dimension demandée.
De plus, si tu désirs centrer ta fenêtre, utilise "jeu.setLocationRelativeTo(null);".
Pour remplacer ta boucle... il faut déjà l'enlever ![]()
Ensuite, il faut transposer ses comportements. Ne pense pas en terme de développement, mais uniquement en terme d'utilisation. Détache-toi du code.
Ensuite, avec ton interface actuel, réfléchis à ce que ton programme faisait en console, et ce que tu veux qu'il fasse avec l'interface.
Normalement, ça devrait venir tout seul ensuite ![]()
Voilà j'ai terminé la modification de mon code, il marche globalement bien mais il reste deux points qui me posent problème.
(je n'ai souhaité supprimer aucune fonction de la prof, meme si je les ai bien modifiées)
Voici le lien:
http://pastebin.com/PRmHqZNU
Et les problèmes:
1) Toujours le meme problème: en lançant le script, la fenetre principale s'affiche toujours correctement, cependant les boutons eux n'apparaissent pas ! J'arrive à regler le problème en stoppant le programme avec ctrl+c(j'utilise la console directement) et en le relançant jusqu'à avoir les boutons... Mais cette solution est assez barbare
2) La variable pasFini ne sert à rien.
En effet, j'ai conservé le code tel quel qui, quand on croise une ligne pasFini=false, est censé arreter tout et afficher le gagnant/perdant. Cependant j'ai essayé de placer des boucles partout avec if pasFini==false/true, dans tous les sens, et j'ai l'impression que ça n'a aucun impact, et le perdant n'est jamais affiché... Idem pour le compteur; on peut jouer plus de 20 tours sans que le jeu ne s'arrete, ce qui est un problème
1) Pas d'idée, de mon coté, ça s'affiche toujours bien.
Mets quand même un setMinimumSize, comme suggéré dans mon message précédent.
2) Dans un jeu de plateau, quand vérifies-tu qu'une partie est terminée ?
Après chaque action des joueurs, tu dois faire la vérification de fin de jeu. Actuellement, elle n'est faite que dans la méthode "joue" qui est invoquée une unique fois : lorsque le jeu commence.
Mais... lis-tu ce que j'écris, par contre ?
J'ai un peu l'impression d'avoir écrit des conseils dans le vide en voyant, entre autre, des "return;" dans une méthode sensée ne rien renvoyer...
J'ai essayé avec le setMinimumSize et ça bug toujours; ça vient peut etre de l'ordi, parce que ça semble réellement aléatoire, mais ce n'est pas vraiment gênant.
Ensuite oui, je lis bien tes messages. Simplement il me semble que le seul return qui ne devrait pas etre là est dans joue(), et comme je l'ai dit, cette méthode ne vient pas de moi, donc je n'ose pas trop y toucher même si elle ne sert plus.
__________________
En fait avant de poster mon message je suis retourné programmer et j'ai a peu près résolu tout ce qui ne fonctionnait pas et voilà le résultat:
http://pastebin.com/j3PteGC9
Evidemment je n'ai pas réussi à faire un truc parfait donc le programme marche très bien... Jusqu'à la fin.
Si jamais aucun joueur ne fait de faute, on est censé avoir un calcul de surface, qui s'effectuait correctement avant(donc dans les fichiers précédent il n'y avait pas de probleme). Ici, il se trouve que j'ai une erreur de pointeur nul que je n'arrive pas à résoudre.
Donc normalement voilà ce que je devrais faire pour que le programme soit totalement fini:
- Résoudre le problème de pointeur nul (généré par la méthode surface, mais celle ci ne pose pas probleme puisque quand le code etait organisé autrement, tout fonctionnait)
- Essayer de "forcer" les utilisateurs à quitter le programme, par exemple en "figeant" la Jframe pour qu'ils ne jouent plus, mais j'ai juste reussi a afficher un message dans la console pour dire que la partie etait finie...
Au fait, j'ai bien viré la méthode joue et les returns qui allaient avec
Quelle ligne indique la NPE ?
Au pire, colle toute la stacktrace dans un pastebin, je te dirai comment la lire ![]()
Je suis plus sur l'ordi où j'ai tout, mais l'erreur était à la ligne où y'a double s1 = j.j1.surface();
"j1" est privée. Tu ne dois pas y accéder comme ça, je suis même surpris que ça compile.
Crée un getter pour ces variables (j1 et j2) et utilises-les.
Ca n'enlèvera pas forcément l'erreur, mais ça t'en enlèvera une potentielle.
La NPE est peut-être déclenchée dans ta méthode surface, que je ne connais pas, n'y ayant pas accès.
J'essayerai ça demain, mais par contre c'est bizarre parce qu'à "l'époque" où j'avais laissé la méthode joue(), le calcul de surface se faisait de la meme manière, mais il fonctionnait, alors que plus maintenant, c'est ça que je trouve étrange! Donc dans les pastebin précédents ça marchait encore me semble t il
Possib'.
N'ayant pas la la classe joueur, je ne sais pas.
Je voudrai bien voir la stacktrace complète, quand même ![]()